summaryrefslogtreecommitdiff
path: root/chromium/content/browser/renderer_host
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/content/browser/renderer_host')
-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
338 files changed, 87373 insertions, 0 deletions
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