From c407dc5cd9bdc5668497f21b26b09d988ab439de Mon Sep 17 00:00:00 2001 From: Ben Murdoch Date: Thu, 29 Jul 2010 17:14:53 +0100 Subject: Merge Chromium src@r53293 Change-Id: Ia79acf8670f385cee48c45b0a75371d8e950af34 --- webkit/glue/DEPS | 11 + webkit/glue/alt_error_page_resource_fetcher.cc | 51 + webkit/glue/alt_error_page_resource_fetcher.h | 59 + webkit/glue/bookmarklet_unittest.cc | 76 ++ webkit/glue/context_menu.h | 129 ++ webkit/glue/context_menu_unittest.cc | 67 + webkit/glue/cpp_binding_example.cc | 123 ++ webkit/glue/cpp_binding_example.h | 78 ++ webkit/glue/cpp_bound_class.cc | 330 +++++ webkit/glue/cpp_bound_class.h | 181 +++ webkit/glue/cpp_bound_class_unittest.cc | 288 ++++ webkit/glue/cpp_variant.cc | 268 ++++ webkit/glue/cpp_variant.h | 110 ++ webkit/glue/cpp_variant_unittest.cc | 426 ++++++ webkit/glue/devtools_message_data.cc | 30 + webkit/glue/devtools_message_data.h | 25 + webkit/glue/devtools_strings.grd | 154 +++ webkit/glue/dom_operations.cc | 619 +++++++++ webkit/glue/dom_operations.h | 144 ++ webkit/glue/dom_operations_unittest.cc | 184 +++ webkit/glue/dom_serializer_unittest.cc | 850 ++++++++++++ webkit/glue/form_data.h | 41 + webkit/glue/form_field.cc | 103 ++ webkit/glue/form_field.h | 73 + .../ftp_directory_listing_response_delegate.cc | 159 +++ .../glue/ftp_directory_listing_response_delegate.h | 68 + webkit/glue/glue_serialize.cc | 473 +++++++ webkit/glue/glue_serialize.h | 31 + webkit/glue/glue_serialize_unittest.cc | 200 +++ webkit/glue/iframe_redirect_unittest.cc | 48 + webkit/glue/image_decoder.cc | 41 + webkit/glue/image_decoder.h | 37 + webkit/glue/image_resource_fetcher.cc | 54 + webkit/glue/image_resource_fetcher.h | 63 + webkit/glue/inspector_strings.grd | 640 +++++++++ webkit/glue/media/buffered_data_source.cc | 1052 +++++++++++++++ webkit/glue/media/buffered_data_source.h | 413 ++++++ webkit/glue/media/buffered_data_source_unittest.cc | 934 +++++++++++++ .../media/media_resource_loader_bridge_factory.cc | 76 ++ .../media/media_resource_loader_bridge_factory.h | 76 ++ ...edia_resource_loader_bridge_factory_unittest.cc | 44 + .../mock_media_resource_loader_bridge_factory.h | 36 + webkit/glue/media/simple_data_source.cc | 236 ++++ webkit/glue/media/simple_data_source.h | 125 ++ webkit/glue/media/simple_data_source_unittest.cc | 252 ++++ webkit/glue/media/video_renderer_impl.cc | 321 +++++ webkit/glue/media/video_renderer_impl.h | 122 ++ webkit/glue/media/web_video_renderer.h | 39 + webkit/glue/mimetype_unittest.cc | 91 ++ webkit/glue/mock_resource_loader_bridge.h | 42 + webkit/glue/multipart_response_delegate.cc | 373 ++++++ webkit/glue/multipart_response_delegate.h | 147 ++ .../glue/multipart_response_delegate_unittest.cc | 638 +++++++++ webkit/glue/npruntime_util.cc | 51 + webkit/glue/npruntime_util.h | 21 + webkit/glue/password_form.h | 168 +++ webkit/glue/password_form_dom_manager.cc | 70 + webkit/glue/password_form_dom_manager.h | 63 + webkit/glue/plugins/DEPS | 4 + .../plugins/carbon_plugin_window_tracker_mac.cc | 55 + .../plugins/carbon_plugin_window_tracker_mac.h | 51 + .../plugins/coregraphics_private_symbols_mac.h | 27 + webkit/glue/plugins/default_plugin_shared.h | 31 + webkit/glue/plugins/gtk_plugin_container.cc | 85 ++ webkit/glue/plugins/gtk_plugin_container.h | 26 + .../glue/plugins/gtk_plugin_container_manager.cc | 147 ++ webkit/glue/plugins/gtk_plugin_container_manager.h | 56 + .../plugins/mac_accelerated_surface_container.cc | 158 +++ .../plugins/mac_accelerated_surface_container.h | 110 ++ .../mac_accelerated_surface_container_manager.cc | 103 ++ .../mac_accelerated_surface_container_manager.h | 78 ++ webkit/glue/plugins/npapi_extension_thunk.cc | 551 ++++++++ webkit/glue/plugins/npapi_extension_thunk.h | 23 + webkit/glue/plugins/pepper_buffer.cc | 116 ++ webkit/glue/plugins/pepper_buffer.h | 55 + webkit/glue/plugins/pepper_device_context_2d.cc | 553 ++++++++ webkit/glue/plugins/pepper_device_context_2d.h | 175 +++ webkit/glue/plugins/pepper_directory_reader.cc | 68 + webkit/glue/plugins/pepper_directory_reader.h | 37 + webkit/glue/plugins/pepper_event_conversion.cc | 235 ++++ webkit/glue/plugins/pepper_event_conversion.h | 26 + webkit/glue/plugins/pepper_file_chooser.cc | 88 ++ webkit/glue/plugins/pepper_file_chooser.h | 40 + webkit/glue/plugins/pepper_file_io.cc | 255 ++++ webkit/glue/plugins/pepper_file_io.h | 69 + webkit/glue/plugins/pepper_file_ref.cc | 169 +++ webkit/glue/plugins/pepper_file_ref.h | 55 + webkit/glue/plugins/pepper_file_system.cc | 59 + webkit/glue/plugins/pepper_file_system.h | 21 + webkit/glue/plugins/pepper_font.cc | 99 ++ webkit/glue/plugins/pepper_font.h | 39 + webkit/glue/plugins/pepper_image_data.cc | 159 +++ webkit/glue/plugins/pepper_image_data.h | 116 ++ webkit/glue/plugins/pepper_plugin_delegate.h | 59 + webkit/glue/plugins/pepper_plugin_instance.cc | 828 ++++++++++++ webkit/glue/plugins/pepper_plugin_instance.h | 210 +++ webkit/glue/plugins/pepper_plugin_module.cc | 362 +++++ webkit/glue/plugins/pepper_plugin_module.h | 102 ++ webkit/glue/plugins/pepper_private.cc | 38 + webkit/glue/plugins/pepper_private.h | 23 + webkit/glue/plugins/pepper_resource.cc | 19 + webkit/glue/plugins/pepper_resource.h | 135 ++ webkit/glue/plugins/pepper_resource_tracker.cc | 61 + webkit/glue/plugins/pepper_resource_tracker.h | 75 ++ webkit/glue/plugins/pepper_scrollbar.cc | 228 ++++ webkit/glue/plugins/pepper_scrollbar.h | 64 + webkit/glue/plugins/pepper_string.h | 30 + webkit/glue/plugins/pepper_url_loader.cc | 299 +++++ webkit/glue/plugins/pepper_url_loader.h | 89 ++ webkit/glue/plugins/pepper_url_request_info.cc | 220 +++ webkit/glue/plugins/pepper_url_request_info.h | 83 ++ webkit/glue/plugins/pepper_url_response_info.cc | 113 ++ webkit/glue/plugins/pepper_url_response_info.h | 47 + webkit/glue/plugins/pepper_var.cc | 857 ++++++++++++ webkit/glue/plugins/pepper_var.h | 46 + webkit/glue/plugins/pepper_webplugin_impl.cc | 196 +++ webkit/glue/plugins/pepper_webplugin_impl.h | 98 ++ webkit/glue/plugins/pepper_widget.cc | 91 ++ webkit/glue/plugins/pepper_widget.h | 53 + webkit/glue/plugins/plugin_constants_win.h | 41 + webkit/glue/plugins/plugin_host.cc | 1088 +++++++++++++++ webkit/glue/plugins/plugin_host.h | 63 + webkit/glue/plugins/plugin_instance.cc | 632 +++++++++ webkit/glue/plugins/plugin_instance.h | 360 +++++ webkit/glue/plugins/plugin_instance_mac.mm | 133 ++ webkit/glue/plugins/plugin_lib.cc | 315 +++++ webkit/glue/plugins/plugin_lib.h | 120 ++ webkit/glue/plugins/plugin_lib_mac.mm | 346 +++++ webkit/glue/plugins/plugin_lib_posix.cc | 255 ++++ webkit/glue/plugins/plugin_lib_unittest.cc | 151 +++ webkit/glue/plugins/plugin_lib_win.cc | 41 + webkit/glue/plugins/plugin_list.cc | 460 +++++++ webkit/glue/plugins/plugin_list.h | 275 ++++ webkit/glue/plugins/plugin_list_mac.mm | 107 ++ webkit/glue/plugins/plugin_list_posix.cc | 267 ++++ webkit/glue/plugins/plugin_list_win.cc | 404 ++++++ webkit/glue/plugins/plugin_stream.cc | 254 ++++ webkit/glue/plugins/plugin_stream.h | 146 ++ webkit/glue/plugins/plugin_stream_posix.cc | 74 ++ webkit/glue/plugins/plugin_stream_url.cc | 106 ++ webkit/glue/plugins/plugin_stream_url.h | 69 + webkit/glue/plugins/plugin_stream_win.cc | 96 ++ webkit/glue/plugins/plugin_string_stream.cc | 37 + webkit/glue/plugins/plugin_string_stream.h | 39 + webkit/glue/plugins/plugin_stubs.cc | 30 + webkit/glue/plugins/plugin_switches.cc | 15 + webkit/glue/plugins/plugin_switches.h | 15 + .../glue/plugins/plugin_web_event_converter_mac.h | 66 + .../glue/plugins/plugin_web_event_converter_mac.mm | 396 ++++++ webkit/glue/plugins/ppb_private.h | 21 + .../glue/plugins/quickdraw_drawing_manager_mac.cc | 154 +++ .../glue/plugins/quickdraw_drawing_manager_mac.h | 83 ++ webkit/glue/plugins/test/Info.plist | 46 + webkit/glue/plugins/test/npapi_constants.cc | 10 + webkit/glue/plugins/test/npapi_constants.h | 19 + webkit/glue/plugins/test/npapi_test.cc | 80 ++ webkit/glue/plugins/test/npapi_test.def | 6 + webkit/glue/plugins/test/npapi_test.rc | 102 ++ webkit/glue/plugins/test/plugin_arguments_test.cc | 68 + webkit/glue/plugins/test/plugin_arguments_test.h | 43 + webkit/glue/plugins/test/plugin_client.cc | 230 ++++ webkit/glue/plugins/test/plugin_client.h | 45 + .../test/plugin_create_instance_in_paint.cc | 76 ++ .../plugins/test/plugin_create_instance_in_paint.h | 33 + .../test/plugin_delete_plugin_in_stream_test.cc | 45 + .../test/plugin_delete_plugin_in_stream_test.h | 30 + .../test/plugin_get_javascript_url2_test.cc | 134 ++ .../plugins/test/plugin_get_javascript_url2_test.h | 38 + .../plugins/test/plugin_get_javascript_url_test.cc | 210 +++ .../plugins/test/plugin_get_javascript_url_test.h | 47 + webkit/glue/plugins/test/plugin_geturl_test.cc | 374 ++++++ webkit/glue/plugins/test/plugin_geturl_test.h | 55 + .../plugins/test/plugin_javascript_open_popup.cc | 103 ++ .../plugins/test/plugin_javascript_open_popup.h | 47 + webkit/glue/plugins/test/plugin_new_fails_test.cc | 18 + webkit/glue/plugins/test/plugin_new_fails_test.h | 21 + .../plugins/test/plugin_npobject_lifetime_test.cc | 170 +++ .../plugins/test/plugin_npobject_lifetime_test.h | 81 ++ .../plugins/test/plugin_npobject_proxy_test.cc | 51 + .../glue/plugins/test/plugin_npobject_proxy_test.h | 27 + webkit/glue/plugins/test/plugin_private_test.cc | 57 + webkit/glue/plugins/test/plugin_private_test.h | 25 + .../plugins/test/plugin_schedule_timer_test.cc | 116 ++ .../glue/plugins/test/plugin_schedule_timer_test.h | 70 + webkit/glue/plugins/test/plugin_test.cc | 150 +++ webkit/glue/plugins/test/plugin_test.h | 131 ++ webkit/glue/plugins/test/plugin_test_factory.cc | 97 ++ webkit/glue/plugins/test/plugin_test_factory.h | 22 + .../plugins/test/plugin_thread_async_call_test.cc | 116 ++ .../plugins/test/plugin_thread_async_call_test.h | 38 + .../glue/plugins/test/plugin_window_size_test.cc | 55 + webkit/glue/plugins/test/plugin_window_size_test.h | 24 + webkit/glue/plugins/test/plugin_windowed_test.cc | 139 ++ webkit/glue/plugins/test/plugin_windowed_test.h | 33 + webkit/glue/plugins/test/plugin_windowless_test.cc | 260 ++++ webkit/glue/plugins/test/plugin_windowless_test.h | 35 + webkit/glue/plugins/test/resource.h | 15 + webkit/glue/plugins/webplugin.cc | 23 + webkit/glue/plugins/webplugin.h | 195 +++ webkit/glue/plugins/webplugin_2d_device_delegate.h | 57 + webkit/glue/plugins/webplugin_3d_device_delegate.h | 101 ++ .../glue/plugins/webplugin_audio_device_delegate.h | 56 + webkit/glue/plugins/webplugin_delegate.h | 166 +++ webkit/glue/plugins/webplugin_delegate_impl.cc | 268 ++++ webkit/glue/plugins/webplugin_delegate_impl.h | 490 +++++++ webkit/glue/plugins/webplugin_delegate_impl_gtk.cc | 709 ++++++++++ webkit/glue/plugins/webplugin_delegate_impl_mac.mm | 1160 ++++++++++++++++ webkit/glue/plugins/webplugin_delegate_impl_win.cc | 1397 ++++++++++++++++++++ webkit/glue/plugins/webplugin_file_delegate.h | 35 + webkit/glue/plugins/webplugin_impl.cc | 1305 ++++++++++++++++++ webkit/glue/plugins/webplugin_impl.h | 332 +++++ webkit/glue/plugins/webplugin_impl_unittest.cc | 232 ++++ webkit/glue/plugins/webplugin_page_delegate.h | 69 + webkit/glue/plugins/webplugin_print_delegate.h | 49 + webkit/glue/plugins/webplugininfo.h | 47 + webkit/glue/plugins/webview_plugin.cc | 142 ++ webkit/glue/plugins/webview_plugin.h | 107 ++ webkit/glue/regular_expression_unittest.cc | 105 ++ webkit/glue/resource_fetcher.cc | 129 ++ webkit/glue/resource_fetcher.h | 117 ++ webkit/glue/resource_fetcher_unittest.cc | 234 ++++ webkit/glue/resource_loader_bridge.cc | 68 + webkit/glue/resource_loader_bridge.h | 345 +++++ webkit/glue/resource_type.h | 59 + webkit/glue/resources/README.txt | 97 ++ webkit/glue/resources/aliasb.cur | Bin 0 -> 326 bytes webkit/glue/resources/broken-image.gif | Bin 0 -> 165 bytes webkit/glue/resources/cell.cur | Bin 0 -> 326 bytes webkit/glue/resources/col_resize.cur | Bin 0 -> 326 bytes webkit/glue/resources/copy.cur | Bin 0 -> 326 bytes webkit/glue/resources/dash.png | Bin 0 -> 122 bytes .../linux-checkbox-disabled-indeterminate.png | Bin 0 -> 3914 bytes .../glue/resources/linux-checkbox-disabled-off.png | Bin 0 -> 2850 bytes .../glue/resources/linux-checkbox-disabled-on.png | Bin 0 -> 3015 bytes .../resources/linux-checkbox-indeterminate.png | Bin 0 -> 3098 bytes webkit/glue/resources/linux-checkbox-off.png | Bin 0 -> 3024 bytes webkit/glue/resources/linux-checkbox-on.png | Bin 0 -> 3165 bytes webkit/glue/resources/linux-progress-bar.png | Bin 0 -> 182 bytes .../glue/resources/linux-progress-border-left.png | Bin 0 -> 148 bytes .../glue/resources/linux-progress-border-right.png | Bin 0 -> 146 bytes webkit/glue/resources/linux-progress-value.png | Bin 0 -> 167 bytes webkit/glue/resources/linux-radio-disabled-off.png | Bin 0 -> 2910 bytes webkit/glue/resources/linux-radio-disabled-on.png | Bin 0 -> 3015 bytes webkit/glue/resources/linux-radio-off.png | Bin 0 -> 3133 bytes webkit/glue/resources/linux-radio-on.png | Bin 0 -> 3148 bytes webkit/glue/resources/media_pause.png | Bin 0 -> 153 bytes webkit/glue/resources/media_play.png | Bin 0 -> 261 bytes webkit/glue/resources/media_play_disabled.png | Bin 0 -> 237 bytes webkit/glue/resources/media_slider_thumb.png | Bin 0 -> 324 bytes webkit/glue/resources/media_sound_disabled.png | Bin 0 -> 307 bytes webkit/glue/resources/media_sound_full.png | Bin 0 -> 461 bytes webkit/glue/resources/media_sound_none.png | Bin 0 -> 301 bytes .../glue/resources/media_volume_slider_thumb.png | Bin 0 -> 100 bytes webkit/glue/resources/pan_east.cur | Bin 0 -> 4286 bytes webkit/glue/resources/pan_icon.png | Bin 0 -> 238 bytes webkit/glue/resources/pan_middle.cur | Bin 0 -> 4286 bytes webkit/glue/resources/pan_north.cur | Bin 0 -> 4286 bytes webkit/glue/resources/pan_north_east.cur | Bin 0 -> 4286 bytes webkit/glue/resources/pan_north_west.cur | Bin 0 -> 4286 bytes webkit/glue/resources/pan_south.cur | Bin 0 -> 4286 bytes webkit/glue/resources/pan_south_east.cur | Bin 0 -> 4286 bytes webkit/glue/resources/pan_south_west.cur | Bin 0 -> 4286 bytes webkit/glue/resources/pan_west.cur | Bin 0 -> 4286 bytes webkit/glue/resources/row_resize.cur | Bin 0 -> 326 bytes webkit/glue/resources/search_cancel.png | Bin 0 -> 908 bytes webkit/glue/resources/search_cancel_pressed.png | Bin 0 -> 895 bytes webkit/glue/resources/search_magnifier.png | Bin 0 -> 1028 bytes webkit/glue/resources/search_magnifier_results.png | Bin 0 -> 1224 bytes webkit/glue/resources/textarea_resize_corner.png | Bin 0 -> 195 bytes webkit/glue/resources/vertical_text.cur | Bin 0 -> 326 bytes webkit/glue/resources/webkit_strings_am.xtb | 43 + webkit/glue/resources/webkit_strings_ar.xtb | 43 + webkit/glue/resources/webkit_strings_bg.xtb | 43 + webkit/glue/resources/webkit_strings_bn.xtb | 43 + webkit/glue/resources/webkit_strings_ca.xtb | 43 + webkit/glue/resources/webkit_strings_cs.xtb | 43 + webkit/glue/resources/webkit_strings_da.xtb | 43 + webkit/glue/resources/webkit_strings_de.xtb | 43 + webkit/glue/resources/webkit_strings_el.xtb | 43 + webkit/glue/resources/webkit_strings_en-GB.xtb | 43 + webkit/glue/resources/webkit_strings_es-419.xtb | 43 + webkit/glue/resources/webkit_strings_es.xtb | 43 + webkit/glue/resources/webkit_strings_et.xtb | 43 + webkit/glue/resources/webkit_strings_fi.xtb | 43 + webkit/glue/resources/webkit_strings_fil.xtb | 43 + webkit/glue/resources/webkit_strings_fr.xtb | 43 + webkit/glue/resources/webkit_strings_gu.xtb | 43 + webkit/glue/resources/webkit_strings_hi.xtb | 43 + webkit/glue/resources/webkit_strings_hr.xtb | 43 + webkit/glue/resources/webkit_strings_hu.xtb | 43 + webkit/glue/resources/webkit_strings_id.xtb | 43 + webkit/glue/resources/webkit_strings_it.xtb | 43 + webkit/glue/resources/webkit_strings_iw.xtb | 43 + webkit/glue/resources/webkit_strings_ja.xtb | 43 + webkit/glue/resources/webkit_strings_kn.xtb | 43 + webkit/glue/resources/webkit_strings_ko.xtb | 43 + webkit/glue/resources/webkit_strings_lt.xtb | 43 + webkit/glue/resources/webkit_strings_lv.xtb | 43 + webkit/glue/resources/webkit_strings_ml.xtb | 43 + webkit/glue/resources/webkit_strings_mr.xtb | 43 + webkit/glue/resources/webkit_strings_nl.xtb | 43 + webkit/glue/resources/webkit_strings_no.xtb | 43 + webkit/glue/resources/webkit_strings_pl.xtb | 43 + webkit/glue/resources/webkit_strings_pt-BR.xtb | 43 + webkit/glue/resources/webkit_strings_pt-PT.xtb | 43 + webkit/glue/resources/webkit_strings_ro.xtb | 43 + webkit/glue/resources/webkit_strings_ru.xtb | 43 + webkit/glue/resources/webkit_strings_sk.xtb | 43 + webkit/glue/resources/webkit_strings_sl.xtb | 43 + webkit/glue/resources/webkit_strings_sr.xtb | 43 + webkit/glue/resources/webkit_strings_sv.xtb | 43 + webkit/glue/resources/webkit_strings_sw.xtb | 43 + webkit/glue/resources/webkit_strings_ta.xtb | 43 + webkit/glue/resources/webkit_strings_te.xtb | 43 + webkit/glue/resources/webkit_strings_th.xtb | 43 + webkit/glue/resources/webkit_strings_tr.xtb | 43 + webkit/glue/resources/webkit_strings_uk.xtb | 43 + webkit/glue/resources/webkit_strings_vi.xtb | 43 + webkit/glue/resources/webkit_strings_zh-CN.xtb | 43 + webkit/glue/resources/webkit_strings_zh-TW.xtb | 43 + webkit/glue/resources/zoom_in.cur | Bin 0 -> 326 bytes webkit/glue/resources/zoom_out.cur | Bin 0 -> 326 bytes webkit/glue/scoped_clipboard_writer_glue.h | 32 + webkit/glue/simple_webmimeregistry_impl.cc | 117 ++ webkit/glue/simple_webmimeregistry_impl.h | 33 + webkit/glue/site_isolation_metrics.cc | 231 ++++ webkit/glue/site_isolation_metrics.h | 41 + webkit/glue/unittest_test_server.h | 65 + webkit/glue/webaccessibility.cc | 300 +++++ webkit/glue/webaccessibility.h | 187 +++ webkit/glue/webclipboard_impl.cc | 231 ++++ webkit/glue/webclipboard_impl.h | 55 + webkit/glue/webcookie.h | 77 ++ webkit/glue/webcursor.cc | 194 +++ webkit/glue/webcursor.h | 149 +++ webkit/glue/webcursor_gtk.cc | 218 +++ webkit/glue/webcursor_gtk_data.h | 253 ++++ webkit/glue/webcursor_mac.mm | 384 ++++++ webkit/glue/webcursor_unittest.cc | 82 ++ webkit/glue/webcursor_win.cc | 236 ++++ webkit/glue/webdropdata.cc | 52 + webkit/glue/webdropdata.h | 73 + webkit/glue/webdropdata_win.cc | 33 + webkit/glue/webfilesystem_impl.cc | 154 +++ webkit/glue/webfilesystem_impl.h | 54 + webkit/glue/webframe_unittest.cc | 95 ++ webkit/glue/webkit_glue.cc | 509 +++++++ webkit/glue/webkit_glue.gypi | 420 ++++++ webkit/glue/webkit_glue.h | 293 ++++ webkit/glue/webkit_glue_dummy.cc | 20 + webkit/glue/webkit_glue_unittest.cc | 37 + webkit/glue/webkit_resources.grd | 64 + webkit/glue/webkit_strings.grd | 364 +++++ webkit/glue/webkitclient_impl.cc | 437 ++++++ webkit/glue/webkitclient_impl.h | 80 ++ webkit/glue/webmediaplayer_impl.cc | 711 ++++++++++ webkit/glue/webmediaplayer_impl.h | 340 +++++ webkit/glue/webmenuitem.h | 39 + webkit/glue/webmenurunner_mac.h | 70 + webkit/glue/webmenurunner_mac.mm | 204 +++ .../glue/webpasswordautocompletelistener_impl.cc | 197 +++ webkit/glue/webpasswordautocompletelistener_impl.h | 86 ++ .../webpasswordautocompletelistener_unittest.cc | 291 ++++ webkit/glue/webpreferences.cc | 110 ++ webkit/glue/webpreferences.h | 120 ++ webkit/glue/websocketstreamhandle_bridge.h | 47 + webkit/glue/websocketstreamhandle_delegate.h | 37 + webkit/glue/websocketstreamhandle_impl.cc | 169 +++ webkit/glue/websocketstreamhandle_impl.h | 34 + webkit/glue/webthemeengine_impl_win.cc | 142 ++ webkit/glue/webthemeengine_impl_win.h | 48 + webkit/glue/weburlloader_impl.cc | 688 ++++++++++ webkit/glue/weburlloader_impl.h | 37 + webkit/glue/webview_unittest.cc | 28 + webkit/glue/window_open_disposition.cc | 30 + webkit/glue/window_open_disposition.h | 28 + 376 files changed, 49510 insertions(+) create mode 100644 webkit/glue/DEPS create mode 100644 webkit/glue/alt_error_page_resource_fetcher.cc create mode 100644 webkit/glue/alt_error_page_resource_fetcher.h create mode 100644 webkit/glue/bookmarklet_unittest.cc create mode 100644 webkit/glue/context_menu.h create mode 100644 webkit/glue/context_menu_unittest.cc create mode 100644 webkit/glue/cpp_binding_example.cc create mode 100644 webkit/glue/cpp_binding_example.h create mode 100644 webkit/glue/cpp_bound_class.cc create mode 100644 webkit/glue/cpp_bound_class.h create mode 100644 webkit/glue/cpp_bound_class_unittest.cc create mode 100644 webkit/glue/cpp_variant.cc create mode 100644 webkit/glue/cpp_variant.h create mode 100644 webkit/glue/cpp_variant_unittest.cc create mode 100644 webkit/glue/devtools_message_data.cc create mode 100644 webkit/glue/devtools_message_data.h create mode 100644 webkit/glue/devtools_strings.grd create mode 100644 webkit/glue/dom_operations.cc create mode 100644 webkit/glue/dom_operations.h create mode 100644 webkit/glue/dom_operations_unittest.cc create mode 100644 webkit/glue/dom_serializer_unittest.cc create mode 100644 webkit/glue/form_data.h create mode 100644 webkit/glue/form_field.cc create mode 100644 webkit/glue/form_field.h create mode 100644 webkit/glue/ftp_directory_listing_response_delegate.cc create mode 100644 webkit/glue/ftp_directory_listing_response_delegate.h create mode 100644 webkit/glue/glue_serialize.cc create mode 100644 webkit/glue/glue_serialize.h create mode 100644 webkit/glue/glue_serialize_unittest.cc create mode 100644 webkit/glue/iframe_redirect_unittest.cc create mode 100644 webkit/glue/image_decoder.cc create mode 100644 webkit/glue/image_decoder.h create mode 100644 webkit/glue/image_resource_fetcher.cc create mode 100644 webkit/glue/image_resource_fetcher.h create mode 100644 webkit/glue/inspector_strings.grd create mode 100644 webkit/glue/media/buffered_data_source.cc create mode 100644 webkit/glue/media/buffered_data_source.h create mode 100644 webkit/glue/media/buffered_data_source_unittest.cc create mode 100644 webkit/glue/media/media_resource_loader_bridge_factory.cc create mode 100644 webkit/glue/media/media_resource_loader_bridge_factory.h create mode 100644 webkit/glue/media/media_resource_loader_bridge_factory_unittest.cc create mode 100644 webkit/glue/media/mock_media_resource_loader_bridge_factory.h create mode 100644 webkit/glue/media/simple_data_source.cc create mode 100644 webkit/glue/media/simple_data_source.h create mode 100644 webkit/glue/media/simple_data_source_unittest.cc create mode 100644 webkit/glue/media/video_renderer_impl.cc create mode 100644 webkit/glue/media/video_renderer_impl.h create mode 100644 webkit/glue/media/web_video_renderer.h create mode 100644 webkit/glue/mimetype_unittest.cc create mode 100644 webkit/glue/mock_resource_loader_bridge.h create mode 100644 webkit/glue/multipart_response_delegate.cc create mode 100644 webkit/glue/multipart_response_delegate.h create mode 100644 webkit/glue/multipart_response_delegate_unittest.cc create mode 100644 webkit/glue/npruntime_util.cc create mode 100644 webkit/glue/npruntime_util.h create mode 100644 webkit/glue/password_form.h create mode 100644 webkit/glue/password_form_dom_manager.cc create mode 100644 webkit/glue/password_form_dom_manager.h create mode 100644 webkit/glue/plugins/DEPS create mode 100644 webkit/glue/plugins/carbon_plugin_window_tracker_mac.cc create mode 100644 webkit/glue/plugins/carbon_plugin_window_tracker_mac.h create mode 100644 webkit/glue/plugins/coregraphics_private_symbols_mac.h create mode 100644 webkit/glue/plugins/default_plugin_shared.h create mode 100644 webkit/glue/plugins/gtk_plugin_container.cc create mode 100644 webkit/glue/plugins/gtk_plugin_container.h create mode 100644 webkit/glue/plugins/gtk_plugin_container_manager.cc create mode 100644 webkit/glue/plugins/gtk_plugin_container_manager.h create mode 100644 webkit/glue/plugins/mac_accelerated_surface_container.cc create mode 100644 webkit/glue/plugins/mac_accelerated_surface_container.h create mode 100644 webkit/glue/plugins/mac_accelerated_surface_container_manager.cc create mode 100644 webkit/glue/plugins/mac_accelerated_surface_container_manager.h create mode 100644 webkit/glue/plugins/npapi_extension_thunk.cc create mode 100644 webkit/glue/plugins/npapi_extension_thunk.h create mode 100644 webkit/glue/plugins/pepper_buffer.cc create mode 100644 webkit/glue/plugins/pepper_buffer.h create mode 100644 webkit/glue/plugins/pepper_device_context_2d.cc create mode 100644 webkit/glue/plugins/pepper_device_context_2d.h create mode 100644 webkit/glue/plugins/pepper_directory_reader.cc create mode 100644 webkit/glue/plugins/pepper_directory_reader.h create mode 100644 webkit/glue/plugins/pepper_event_conversion.cc create mode 100644 webkit/glue/plugins/pepper_event_conversion.h create mode 100644 webkit/glue/plugins/pepper_file_chooser.cc create mode 100644 webkit/glue/plugins/pepper_file_chooser.h create mode 100644 webkit/glue/plugins/pepper_file_io.cc create mode 100644 webkit/glue/plugins/pepper_file_io.h create mode 100644 webkit/glue/plugins/pepper_file_ref.cc create mode 100644 webkit/glue/plugins/pepper_file_ref.h create mode 100644 webkit/glue/plugins/pepper_file_system.cc create mode 100644 webkit/glue/plugins/pepper_file_system.h create mode 100644 webkit/glue/plugins/pepper_font.cc create mode 100644 webkit/glue/plugins/pepper_font.h create mode 100644 webkit/glue/plugins/pepper_image_data.cc create mode 100644 webkit/glue/plugins/pepper_image_data.h create mode 100644 webkit/glue/plugins/pepper_plugin_delegate.h create mode 100644 webkit/glue/plugins/pepper_plugin_instance.cc create mode 100644 webkit/glue/plugins/pepper_plugin_instance.h create mode 100644 webkit/glue/plugins/pepper_plugin_module.cc create mode 100644 webkit/glue/plugins/pepper_plugin_module.h create mode 100644 webkit/glue/plugins/pepper_private.cc create mode 100644 webkit/glue/plugins/pepper_private.h create mode 100644 webkit/glue/plugins/pepper_resource.cc create mode 100644 webkit/glue/plugins/pepper_resource.h create mode 100644 webkit/glue/plugins/pepper_resource_tracker.cc create mode 100644 webkit/glue/plugins/pepper_resource_tracker.h create mode 100644 webkit/glue/plugins/pepper_scrollbar.cc create mode 100644 webkit/glue/plugins/pepper_scrollbar.h create mode 100644 webkit/glue/plugins/pepper_string.h create mode 100644 webkit/glue/plugins/pepper_url_loader.cc create mode 100644 webkit/glue/plugins/pepper_url_loader.h create mode 100644 webkit/glue/plugins/pepper_url_request_info.cc create mode 100644 webkit/glue/plugins/pepper_url_request_info.h create mode 100644 webkit/glue/plugins/pepper_url_response_info.cc create mode 100644 webkit/glue/plugins/pepper_url_response_info.h create mode 100644 webkit/glue/plugins/pepper_var.cc create mode 100644 webkit/glue/plugins/pepper_var.h create mode 100644 webkit/glue/plugins/pepper_webplugin_impl.cc create mode 100644 webkit/glue/plugins/pepper_webplugin_impl.h create mode 100644 webkit/glue/plugins/pepper_widget.cc create mode 100644 webkit/glue/plugins/pepper_widget.h create mode 100644 webkit/glue/plugins/plugin_constants_win.h create mode 100644 webkit/glue/plugins/plugin_host.cc create mode 100644 webkit/glue/plugins/plugin_host.h create mode 100644 webkit/glue/plugins/plugin_instance.cc create mode 100644 webkit/glue/plugins/plugin_instance.h create mode 100644 webkit/glue/plugins/plugin_instance_mac.mm create mode 100644 webkit/glue/plugins/plugin_lib.cc create mode 100644 webkit/glue/plugins/plugin_lib.h create mode 100644 webkit/glue/plugins/plugin_lib_mac.mm create mode 100644 webkit/glue/plugins/plugin_lib_posix.cc create mode 100644 webkit/glue/plugins/plugin_lib_unittest.cc create mode 100644 webkit/glue/plugins/plugin_lib_win.cc create mode 100644 webkit/glue/plugins/plugin_list.cc create mode 100644 webkit/glue/plugins/plugin_list.h create mode 100644 webkit/glue/plugins/plugin_list_mac.mm create mode 100644 webkit/glue/plugins/plugin_list_posix.cc create mode 100644 webkit/glue/plugins/plugin_list_win.cc create mode 100644 webkit/glue/plugins/plugin_stream.cc create mode 100644 webkit/glue/plugins/plugin_stream.h create mode 100644 webkit/glue/plugins/plugin_stream_posix.cc create mode 100644 webkit/glue/plugins/plugin_stream_url.cc create mode 100644 webkit/glue/plugins/plugin_stream_url.h create mode 100644 webkit/glue/plugins/plugin_stream_win.cc create mode 100644 webkit/glue/plugins/plugin_string_stream.cc create mode 100644 webkit/glue/plugins/plugin_string_stream.h create mode 100644 webkit/glue/plugins/plugin_stubs.cc create mode 100644 webkit/glue/plugins/plugin_switches.cc create mode 100644 webkit/glue/plugins/plugin_switches.h create mode 100644 webkit/glue/plugins/plugin_web_event_converter_mac.h create mode 100644 webkit/glue/plugins/plugin_web_event_converter_mac.mm create mode 100644 webkit/glue/plugins/ppb_private.h create mode 100644 webkit/glue/plugins/quickdraw_drawing_manager_mac.cc create mode 100644 webkit/glue/plugins/quickdraw_drawing_manager_mac.h create mode 100644 webkit/glue/plugins/test/Info.plist create mode 100644 webkit/glue/plugins/test/npapi_constants.cc create mode 100644 webkit/glue/plugins/test/npapi_constants.h create mode 100644 webkit/glue/plugins/test/npapi_test.cc create mode 100644 webkit/glue/plugins/test/npapi_test.def create mode 100644 webkit/glue/plugins/test/npapi_test.rc create mode 100644 webkit/glue/plugins/test/plugin_arguments_test.cc create mode 100644 webkit/glue/plugins/test/plugin_arguments_test.h create mode 100644 webkit/glue/plugins/test/plugin_client.cc create mode 100644 webkit/glue/plugins/test/plugin_client.h create mode 100644 webkit/glue/plugins/test/plugin_create_instance_in_paint.cc create mode 100644 webkit/glue/plugins/test/plugin_create_instance_in_paint.h create mode 100644 webkit/glue/plugins/test/plugin_delete_plugin_in_stream_test.cc create mode 100644 webkit/glue/plugins/test/plugin_delete_plugin_in_stream_test.h create mode 100644 webkit/glue/plugins/test/plugin_get_javascript_url2_test.cc create mode 100644 webkit/glue/plugins/test/plugin_get_javascript_url2_test.h create mode 100644 webkit/glue/plugins/test/plugin_get_javascript_url_test.cc create mode 100644 webkit/glue/plugins/test/plugin_get_javascript_url_test.h create mode 100644 webkit/glue/plugins/test/plugin_geturl_test.cc create mode 100644 webkit/glue/plugins/test/plugin_geturl_test.h create mode 100644 webkit/glue/plugins/test/plugin_javascript_open_popup.cc create mode 100644 webkit/glue/plugins/test/plugin_javascript_open_popup.h create mode 100644 webkit/glue/plugins/test/plugin_new_fails_test.cc create mode 100644 webkit/glue/plugins/test/plugin_new_fails_test.h create mode 100644 webkit/glue/plugins/test/plugin_npobject_lifetime_test.cc create mode 100644 webkit/glue/plugins/test/plugin_npobject_lifetime_test.h create mode 100644 webkit/glue/plugins/test/plugin_npobject_proxy_test.cc create mode 100644 webkit/glue/plugins/test/plugin_npobject_proxy_test.h create mode 100644 webkit/glue/plugins/test/plugin_private_test.cc create mode 100644 webkit/glue/plugins/test/plugin_private_test.h create mode 100644 webkit/glue/plugins/test/plugin_schedule_timer_test.cc create mode 100644 webkit/glue/plugins/test/plugin_schedule_timer_test.h create mode 100644 webkit/glue/plugins/test/plugin_test.cc create mode 100644 webkit/glue/plugins/test/plugin_test.h create mode 100644 webkit/glue/plugins/test/plugin_test_factory.cc create mode 100644 webkit/glue/plugins/test/plugin_test_factory.h create mode 100644 webkit/glue/plugins/test/plugin_thread_async_call_test.cc create mode 100644 webkit/glue/plugins/test/plugin_thread_async_call_test.h create mode 100644 webkit/glue/plugins/test/plugin_window_size_test.cc create mode 100644 webkit/glue/plugins/test/plugin_window_size_test.h create mode 100644 webkit/glue/plugins/test/plugin_windowed_test.cc create mode 100644 webkit/glue/plugins/test/plugin_windowed_test.h create mode 100644 webkit/glue/plugins/test/plugin_windowless_test.cc create mode 100644 webkit/glue/plugins/test/plugin_windowless_test.h create mode 100644 webkit/glue/plugins/test/resource.h create mode 100644 webkit/glue/plugins/webplugin.cc create mode 100644 webkit/glue/plugins/webplugin.h create mode 100644 webkit/glue/plugins/webplugin_2d_device_delegate.h create mode 100644 webkit/glue/plugins/webplugin_3d_device_delegate.h create mode 100644 webkit/glue/plugins/webplugin_audio_device_delegate.h create mode 100644 webkit/glue/plugins/webplugin_delegate.h create mode 100644 webkit/glue/plugins/webplugin_delegate_impl.cc create mode 100644 webkit/glue/plugins/webplugin_delegate_impl.h create mode 100644 webkit/glue/plugins/webplugin_delegate_impl_gtk.cc create mode 100644 webkit/glue/plugins/webplugin_delegate_impl_mac.mm create mode 100644 webkit/glue/plugins/webplugin_delegate_impl_win.cc create mode 100644 webkit/glue/plugins/webplugin_file_delegate.h create mode 100644 webkit/glue/plugins/webplugin_impl.cc create mode 100644 webkit/glue/plugins/webplugin_impl.h create mode 100644 webkit/glue/plugins/webplugin_impl_unittest.cc create mode 100644 webkit/glue/plugins/webplugin_page_delegate.h create mode 100644 webkit/glue/plugins/webplugin_print_delegate.h create mode 100644 webkit/glue/plugins/webplugininfo.h create mode 100644 webkit/glue/plugins/webview_plugin.cc create mode 100644 webkit/glue/plugins/webview_plugin.h create mode 100644 webkit/glue/regular_expression_unittest.cc create mode 100644 webkit/glue/resource_fetcher.cc create mode 100644 webkit/glue/resource_fetcher.h create mode 100644 webkit/glue/resource_fetcher_unittest.cc create mode 100644 webkit/glue/resource_loader_bridge.cc create mode 100644 webkit/glue/resource_loader_bridge.h create mode 100644 webkit/glue/resource_type.h create mode 100644 webkit/glue/resources/README.txt create mode 100644 webkit/glue/resources/aliasb.cur create mode 100644 webkit/glue/resources/broken-image.gif create mode 100644 webkit/glue/resources/cell.cur create mode 100644 webkit/glue/resources/col_resize.cur create mode 100644 webkit/glue/resources/copy.cur create mode 100644 webkit/glue/resources/dash.png create mode 100644 webkit/glue/resources/linux-checkbox-disabled-indeterminate.png create mode 100644 webkit/glue/resources/linux-checkbox-disabled-off.png create mode 100644 webkit/glue/resources/linux-checkbox-disabled-on.png create mode 100644 webkit/glue/resources/linux-checkbox-indeterminate.png create mode 100644 webkit/glue/resources/linux-checkbox-off.png create mode 100644 webkit/glue/resources/linux-checkbox-on.png create mode 100644 webkit/glue/resources/linux-progress-bar.png create mode 100644 webkit/glue/resources/linux-progress-border-left.png create mode 100644 webkit/glue/resources/linux-progress-border-right.png create mode 100644 webkit/glue/resources/linux-progress-value.png create mode 100644 webkit/glue/resources/linux-radio-disabled-off.png create mode 100644 webkit/glue/resources/linux-radio-disabled-on.png create mode 100644 webkit/glue/resources/linux-radio-off.png create mode 100644 webkit/glue/resources/linux-radio-on.png create mode 100644 webkit/glue/resources/media_pause.png create mode 100644 webkit/glue/resources/media_play.png create mode 100644 webkit/glue/resources/media_play_disabled.png create mode 100644 webkit/glue/resources/media_slider_thumb.png create mode 100644 webkit/glue/resources/media_sound_disabled.png create mode 100644 webkit/glue/resources/media_sound_full.png create mode 100644 webkit/glue/resources/media_sound_none.png create mode 100644 webkit/glue/resources/media_volume_slider_thumb.png create mode 100644 webkit/glue/resources/pan_east.cur create mode 100644 webkit/glue/resources/pan_icon.png create mode 100644 webkit/glue/resources/pan_middle.cur create mode 100644 webkit/glue/resources/pan_north.cur create mode 100644 webkit/glue/resources/pan_north_east.cur create mode 100644 webkit/glue/resources/pan_north_west.cur create mode 100644 webkit/glue/resources/pan_south.cur create mode 100644 webkit/glue/resources/pan_south_east.cur create mode 100644 webkit/glue/resources/pan_south_west.cur create mode 100644 webkit/glue/resources/pan_west.cur create mode 100644 webkit/glue/resources/row_resize.cur create mode 100644 webkit/glue/resources/search_cancel.png create mode 100644 webkit/glue/resources/search_cancel_pressed.png create mode 100644 webkit/glue/resources/search_magnifier.png create mode 100644 webkit/glue/resources/search_magnifier_results.png create mode 100644 webkit/glue/resources/textarea_resize_corner.png create mode 100644 webkit/glue/resources/vertical_text.cur create mode 100644 webkit/glue/resources/webkit_strings_am.xtb create mode 100644 webkit/glue/resources/webkit_strings_ar.xtb create mode 100644 webkit/glue/resources/webkit_strings_bg.xtb create mode 100644 webkit/glue/resources/webkit_strings_bn.xtb create mode 100644 webkit/glue/resources/webkit_strings_ca.xtb create mode 100644 webkit/glue/resources/webkit_strings_cs.xtb create mode 100644 webkit/glue/resources/webkit_strings_da.xtb create mode 100644 webkit/glue/resources/webkit_strings_de.xtb create mode 100644 webkit/glue/resources/webkit_strings_el.xtb create mode 100644 webkit/glue/resources/webkit_strings_en-GB.xtb create mode 100644 webkit/glue/resources/webkit_strings_es-419.xtb create mode 100644 webkit/glue/resources/webkit_strings_es.xtb create mode 100644 webkit/glue/resources/webkit_strings_et.xtb create mode 100644 webkit/glue/resources/webkit_strings_fi.xtb create mode 100644 webkit/glue/resources/webkit_strings_fil.xtb create mode 100644 webkit/glue/resources/webkit_strings_fr.xtb create mode 100644 webkit/glue/resources/webkit_strings_gu.xtb create mode 100644 webkit/glue/resources/webkit_strings_hi.xtb create mode 100644 webkit/glue/resources/webkit_strings_hr.xtb create mode 100644 webkit/glue/resources/webkit_strings_hu.xtb create mode 100644 webkit/glue/resources/webkit_strings_id.xtb create mode 100644 webkit/glue/resources/webkit_strings_it.xtb create mode 100644 webkit/glue/resources/webkit_strings_iw.xtb create mode 100644 webkit/glue/resources/webkit_strings_ja.xtb create mode 100644 webkit/glue/resources/webkit_strings_kn.xtb create mode 100644 webkit/glue/resources/webkit_strings_ko.xtb create mode 100644 webkit/glue/resources/webkit_strings_lt.xtb create mode 100644 webkit/glue/resources/webkit_strings_lv.xtb create mode 100644 webkit/glue/resources/webkit_strings_ml.xtb create mode 100644 webkit/glue/resources/webkit_strings_mr.xtb create mode 100644 webkit/glue/resources/webkit_strings_nl.xtb create mode 100644 webkit/glue/resources/webkit_strings_no.xtb create mode 100644 webkit/glue/resources/webkit_strings_pl.xtb create mode 100644 webkit/glue/resources/webkit_strings_pt-BR.xtb create mode 100644 webkit/glue/resources/webkit_strings_pt-PT.xtb create mode 100644 webkit/glue/resources/webkit_strings_ro.xtb create mode 100644 webkit/glue/resources/webkit_strings_ru.xtb create mode 100644 webkit/glue/resources/webkit_strings_sk.xtb create mode 100644 webkit/glue/resources/webkit_strings_sl.xtb create mode 100644 webkit/glue/resources/webkit_strings_sr.xtb create mode 100644 webkit/glue/resources/webkit_strings_sv.xtb create mode 100644 webkit/glue/resources/webkit_strings_sw.xtb create mode 100644 webkit/glue/resources/webkit_strings_ta.xtb create mode 100644 webkit/glue/resources/webkit_strings_te.xtb create mode 100644 webkit/glue/resources/webkit_strings_th.xtb create mode 100644 webkit/glue/resources/webkit_strings_tr.xtb create mode 100644 webkit/glue/resources/webkit_strings_uk.xtb create mode 100644 webkit/glue/resources/webkit_strings_vi.xtb create mode 100644 webkit/glue/resources/webkit_strings_zh-CN.xtb create mode 100644 webkit/glue/resources/webkit_strings_zh-TW.xtb create mode 100644 webkit/glue/resources/zoom_in.cur create mode 100644 webkit/glue/resources/zoom_out.cur create mode 100644 webkit/glue/scoped_clipboard_writer_glue.h create mode 100644 webkit/glue/simple_webmimeregistry_impl.cc create mode 100644 webkit/glue/simple_webmimeregistry_impl.h create mode 100644 webkit/glue/site_isolation_metrics.cc create mode 100644 webkit/glue/site_isolation_metrics.h create mode 100644 webkit/glue/unittest_test_server.h create mode 100644 webkit/glue/webaccessibility.cc create mode 100644 webkit/glue/webaccessibility.h create mode 100644 webkit/glue/webclipboard_impl.cc create mode 100644 webkit/glue/webclipboard_impl.h create mode 100644 webkit/glue/webcookie.h create mode 100644 webkit/glue/webcursor.cc create mode 100644 webkit/glue/webcursor.h create mode 100644 webkit/glue/webcursor_gtk.cc create mode 100644 webkit/glue/webcursor_gtk_data.h create mode 100644 webkit/glue/webcursor_mac.mm create mode 100644 webkit/glue/webcursor_unittest.cc create mode 100644 webkit/glue/webcursor_win.cc create mode 100644 webkit/glue/webdropdata.cc create mode 100644 webkit/glue/webdropdata.h create mode 100644 webkit/glue/webdropdata_win.cc create mode 100644 webkit/glue/webfilesystem_impl.cc create mode 100644 webkit/glue/webfilesystem_impl.h create mode 100644 webkit/glue/webframe_unittest.cc create mode 100644 webkit/glue/webkit_glue.cc create mode 100644 webkit/glue/webkit_glue.gypi create mode 100644 webkit/glue/webkit_glue.h create mode 100644 webkit/glue/webkit_glue_dummy.cc create mode 100644 webkit/glue/webkit_glue_unittest.cc create mode 100644 webkit/glue/webkit_resources.grd create mode 100644 webkit/glue/webkit_strings.grd create mode 100644 webkit/glue/webkitclient_impl.cc create mode 100644 webkit/glue/webkitclient_impl.h create mode 100644 webkit/glue/webmediaplayer_impl.cc create mode 100644 webkit/glue/webmediaplayer_impl.h create mode 100644 webkit/glue/webmenuitem.h create mode 100644 webkit/glue/webmenurunner_mac.h create mode 100644 webkit/glue/webmenurunner_mac.mm create mode 100644 webkit/glue/webpasswordautocompletelistener_impl.cc create mode 100644 webkit/glue/webpasswordautocompletelistener_impl.h create mode 100644 webkit/glue/webpasswordautocompletelistener_unittest.cc create mode 100644 webkit/glue/webpreferences.cc create mode 100644 webkit/glue/webpreferences.h create mode 100644 webkit/glue/websocketstreamhandle_bridge.h create mode 100644 webkit/glue/websocketstreamhandle_delegate.h create mode 100644 webkit/glue/websocketstreamhandle_impl.cc create mode 100644 webkit/glue/websocketstreamhandle_impl.h create mode 100644 webkit/glue/webthemeengine_impl_win.cc create mode 100644 webkit/glue/webthemeengine_impl_win.h create mode 100644 webkit/glue/weburlloader_impl.cc create mode 100644 webkit/glue/weburlloader_impl.h create mode 100644 webkit/glue/webview_unittest.cc create mode 100644 webkit/glue/window_open_disposition.cc create mode 100644 webkit/glue/window_open_disposition.h (limited to 'webkit') diff --git a/webkit/glue/DEPS b/webkit/glue/DEPS new file mode 100644 index 0000000..e59afec --- /dev/null +++ b/webkit/glue/DEPS @@ -0,0 +1,11 @@ +include_rules = [ + "+app", + "+media", + "+skia/ext", + "+skia/include", + "+webkit/tools/test_shell", # Needed for test shell tests. + + # This is not actually a directory, but npruntime_util.cc includes a file + # from WebKit starting with this path in JSCore mode. + "+bindings/c", +] diff --git a/webkit/glue/alt_error_page_resource_fetcher.cc b/webkit/glue/alt_error_page_resource_fetcher.cc new file mode 100644 index 0000000..4e1867d --- /dev/null +++ b/webkit/glue/alt_error_page_resource_fetcher.cc @@ -0,0 +1,51 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/glue/alt_error_page_resource_fetcher.h" + +#include "base/callback.h" +#include "webkit/glue/resource_fetcher.h" + +using WebKit::WebFrame; +using WebKit::WebURLError; +using WebKit::WebURLResponse; + +namespace webkit_glue { + +// Number of seconds to wait for the alternate error page server. If it takes +// too long, just use the local error page. +static const int kDownloadTimeoutSec = 3; + +AltErrorPageResourceFetcher::AltErrorPageResourceFetcher( + const GURL& url, + WebFrame* frame, + const WebURLError& original_error, + Callback* callback) + : frame_(frame), + callback_(callback), + original_error_(original_error) { + fetcher_.reset(new ResourceFetcherWithTimeout( + url, frame, kDownloadTimeoutSec, + NewCallback(this, &AltErrorPageResourceFetcher::OnURLFetchComplete))); +} + +AltErrorPageResourceFetcher::~AltErrorPageResourceFetcher() { +} + +void AltErrorPageResourceFetcher::Cancel() { + fetcher_->Cancel(); +} + +void AltErrorPageResourceFetcher::OnURLFetchComplete( + const WebURLResponse& response, + const std::string& data) { + // A null response indicates a network error. + if (!response.isNull() && response.httpStatusCode() == 200) { + callback_->Run(frame_, original_error_, data); + } else { + callback_->Run(frame_, original_error_, std::string()); + } +} + +} // namespace webkit_glue diff --git a/webkit/glue/alt_error_page_resource_fetcher.h b/webkit/glue/alt_error_page_resource_fetcher.h new file mode 100644 index 0000000..86671df --- /dev/null +++ b/webkit/glue/alt_error_page_resource_fetcher.h @@ -0,0 +1,59 @@ +// Copyright (c) 2006-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 WEBKIT_GLUE_ALT_ERROR_PAGE_RESOURCE_FETCHER_H_ +#define WEBKIT_GLUE_ALT_ERROR_PAGE_RESOURCE_FETCHER_H_ + +#include "base/callback.h" +#include "base/scoped_ptr.h" +#include "googleurl/src/gurl.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLError.h" + +namespace WebKit { +class WebFrame; +class WebURLResponse; +} + +namespace webkit_glue { +class ResourceFetcherWithTimeout; + +// Used for downloading alternate dns error pages. Once downloading is done +// (or fails), the webview delegate is notified. +class AltErrorPageResourceFetcher { + public: + // This will be called when the alternative error page has been fetched, + // successfully or not. If there is a failure, the third parameter (the + // data) will be empty. + typedef Callback3::Type Callback; + + AltErrorPageResourceFetcher(const GURL& url, + WebKit::WebFrame* frame, + const WebKit::WebURLError& original_error, + Callback* callback); + ~AltErrorPageResourceFetcher(); + + // Stop any pending loads. + void Cancel(); + + private: + void OnURLFetchComplete(const WebKit::WebURLResponse& response, + const std::string& data); + + // Does the actual fetching. + scoped_ptr fetcher_; + + WebKit::WebFrame* frame_; + scoped_ptr callback_; + + // The error associated with this load. If there's an error talking with the + // alt error page server, we need this to complete the original load. + WebKit::WebURLError original_error_; + + DISALLOW_COPY_AND_ASSIGN(AltErrorPageResourceFetcher); +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_ALT_ERROR_PAGE_RESOURCE_FETCHER_H_ diff --git a/webkit/glue/bookmarklet_unittest.cc b/webkit/glue/bookmarklet_unittest.cc new file mode 100644 index 0000000..5d8a364 --- /dev/null +++ b/webkit/glue/bookmarklet_unittest.cc @@ -0,0 +1,76 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/file_util.h" +#include "base/message_loop.h" +#include "base/path_service.h" +#include "base/string_util.h" +#include "googleurl/src/gurl.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/tools/test_shell/test_shell_test.h" + +namespace { + +class BookmarkletTest : public TestShellTest { + public: + virtual void SetUp() { + TestShellTest::SetUp(); + + test_shell_->LoadURL(GURL("data:text/html,start page")); + test_shell_->WaitTestFinished(); + } +}; + +TEST_F(BookmarkletTest, Redirect) { + test_shell_->LoadURL( + GURL("javascript:location.href='data:text/plain,SUCCESS'")); + test_shell_->WaitTestFinished(); + std::wstring text = test_shell_->GetDocumentText(); + EXPECT_EQ(L"SUCCESS", text); +} + +TEST_F(BookmarkletTest, RedirectVoided) { + // This test should be redundant with the Redirect test above. The point + // here is to emphasize that in either case the assignment to location during + // the evaluation of the script should suppress loading the script result. + // Here, because of the void() wrapping there is no script result. + test_shell_->LoadURL( + GURL("javascript:void(location.href='data:text/plain,SUCCESS')")); + test_shell_->WaitTestFinished(); + std::wstring text = test_shell_->GetDocumentText(); + EXPECT_EQ(L"SUCCESS", text); +} + +TEST_F(BookmarkletTest, NonEmptyResult) { + std::wstring text; + + // TODO(darin): This test fails in a JSC build. WebCore+JSC does not really + // need to support this usage until WebCore supports javascript: URLs that + // generate content (https://bugs.webkit.org/show_bug.cgi?id=14959). It is + // important to note that Safari does not support bookmarklets, and this is + // really an edge case. Our behavior with V8 is consistent with FF and IE. +#if 0 + test_shell_->LoadURL(L"javascript:false"); + MessageLoop::current()->RunAllPending(); + text = test_shell_->GetDocumentText(); + EXPECT_EQ(L"false", text); +#endif + + test_shell_->LoadURL(GURL("javascript:'hello world'")); + MessageLoop::current()->RunAllPending(); + text = test_shell_->GetDocumentText(); + EXPECT_EQ(L"hello world", text); +} + +TEST_F(BookmarkletTest, DocumentWrite) { + test_shell_->LoadURL(GURL( + "javascript:document.open();" + "document.write('hello world');" + "document.close()")); + MessageLoop::current()->RunAllPending(); + std::wstring text = test_shell_->GetDocumentText(); + EXPECT_EQ(L"hello world", text); +} + +} // namespace diff --git a/webkit/glue/context_menu.h b/webkit/glue/context_menu.h new file mode 100644 index 0000000..764fb9d --- /dev/null +++ b/webkit/glue/context_menu.h @@ -0,0 +1,129 @@ +// Copyright (c) 2006-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 WEBKIT_GLUE_CONTEXT_MENU_H_ +#define WEBKIT_GLUE_CONTEXT_MENU_H_ + +#include + +#include "base/basictypes.h" +#include "base/utf_string_conversions.h" +#include "googleurl/src/gurl.h" +#include "webkit/glue/webmenuitem.h" + +#include "third_party/WebKit/WebKit/chromium/public/WebContextMenuData.h" + +// Parameters structure for ViewHostMsg_ContextMenu. +// FIXME(beng): This would be more useful in the future and more efficient +// if the parameters here weren't so literally mapped to what +// they contain for the ContextMenu task. It might be better +// to make the string fields more generic so that this object +// could be used for more contextual actions. +struct ContextMenuParams { + // This is the type of Context Node that the context menu was invoked on. + WebKit::WebContextMenuData::MediaType media_type; + + // These values represent the coordinates of the mouse when the context menu + // was invoked. Coords are relative to the associated RenderView's origin. + int x; + int y; + + // This is the URL of the link that encloses the node the context menu was + // invoked on. + GURL link_url; + + // The link URL to be used ONLY for "copy link address". We don't validate + // this field in the frontend process. + GURL unfiltered_link_url; + + // This is the source URL for the element that the context menu was + // invoked on. Example of elements with source URLs are img, audio, and + // video. + GURL src_url; + + // This is true if the context menu was invoked on a blocked image. + bool is_image_blocked; + + // This is the URL of the top level page that the context menu was invoked + // on. + GURL page_url; + + // This is the URL of the subframe that the context menu was invoked on. + GURL frame_url; + + // These are the parameters for the media element that the context menu + // was invoked on. + int media_flags; + + // This is the text of the selection that the context menu was invoked on. + std::wstring selection_text; + + // The misspelled word under the cursor, if any. Used to generate the + // |dictionary_suggestions| list. + string16 misspelled_word; + + // Suggested replacements for a misspelled word under the cursor. + // This vector gets populated in the render process host + // by intercepting ViewHostMsg_ContextMenu in ResourceMessageFilter + // and populating dictionary_suggestions if the type is EDITABLE + // and the misspelled_word is not empty. + std::vector dictionary_suggestions; + + // If editable, flag for whether spell check is enabled or not. + bool spellcheck_enabled; + + // Whether context is editable. + bool is_editable; + +#if defined(OS_MACOSX) + // Writing direction menu items. + // Currently only used on OS X. + int writing_direction_default; + int writing_direction_left_to_right; + int writing_direction_right_to_left; +#endif // OS_MACOSX + + // These flags indicate to the browser whether the renderer believes it is + // able to perform the corresponding action. + int edit_flags; + + // The security info for the resource we are showing the menu on. + std::string security_info; + + // The character encoding of the frame on which the menu is invoked. + std::string frame_charset; + + std::vector custom_items; + + ContextMenuParams() {} + + ContextMenuParams(const WebKit::WebContextMenuData& data) + : media_type(data.mediaType), + x(data.mousePosition.x), + y(data.mousePosition.y), + link_url(data.linkURL), + unfiltered_link_url(data.linkURL), + src_url(data.srcURL), + is_image_blocked(data.isImageBlocked), + page_url(data.pageURL), + frame_url(data.frameURL), + media_flags(data.mediaFlags), + selection_text(UTF16ToWideHack(data.selectedText)), + misspelled_word(data.misspelledWord), + spellcheck_enabled(data.isSpellCheckingEnabled), + is_editable(data.isEditable), +#if defined(OS_MACOSX) + writing_direction_default(data.writingDirectionDefault), + writing_direction_left_to_right(data.writingDirectionLeftToRight), + writing_direction_right_to_left(data.writingDirectionRightToLeft), +#endif // OS_MACOSX + edit_flags(data.editFlags), + security_info(data.securityInfo), + frame_charset(data.frameEncoding.utf8()) { + for (size_t i = 0; i < data.customItems.size(); ++i) + custom_items.push_back(WebMenuItem(data.customItems[i])); + } +}; + +#endif // WEBKIT_GLUE_CONTEXT_MENU_H_ diff --git a/webkit/glue/context_menu_unittest.cc b/webkit/glue/context_menu_unittest.cc new file mode 100644 index 0000000..9cefc9f --- /dev/null +++ b/webkit/glue/context_menu_unittest.cc @@ -0,0 +1,67 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Tests for displaying context menus in corner cases (and swallowing context +// menu events when appropriate) + +#include + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/message_loop.h" +#include "googleurl/src/gurl.h" +#include "third_party/WebKit/WebKit/chromium/public/WebView.h" +#include "webkit/glue/webkit_glue.h" +#include "webkit/tools/test_shell/test_shell_test.h" +#include "webkit/tools/test_shell/test_webview_delegate.h" + +using WebKit::WebInputEvent; +using WebKit::WebMouseEvent; +using WebKit::WebView; + +// Right clicking inside on an iframe should produce a context menu +class ContextMenuCapturing : public TestShellTest { + protected: + void SetUp() { + TestShellTest::SetUp(); + + iframes_data_dir_ = data_dir_; + iframes_data_dir_ = iframes_data_dir_.AppendASCII("test_shell"); + iframes_data_dir_ = iframes_data_dir_.AppendASCII("iframes"); + ASSERT_TRUE(file_util::PathExists(iframes_data_dir_)); + } + + FilePath iframes_data_dir_; +}; + + +TEST_F(ContextMenuCapturing, ContextMenuCapturing) { + // Make sure we have no stored mouse event state + TestWebViewDelegate* test_delegate = test_shell_->delegate(); + test_delegate->clear_captured_context_menu_events(); + EXPECT_EQ(0U, test_delegate->captured_context_menu_events().size()); + + GURL test_url = GetTestURL(iframes_data_dir_, "testiframe.html"); + test_shell_->LoadURL(test_url); + test_shell_->WaitTestFinished(); + + // Create a right click in the center of the iframe. (I'm hoping this will + // make this a bit more robust in case of some other formatting or other bug.) + WebMouseEvent mouse_event; + mouse_event.type = WebInputEvent::MouseDown; + mouse_event.button = WebMouseEvent::ButtonRight; + mouse_event.x = 250; + mouse_event.y = 250; + mouse_event.globalX = 250; + mouse_event.globalY = 250; + + WebView* webview = test_shell_->webView(); + webview->handleInputEvent(mouse_event); + + // Now simulate the corresponding up event which should display the menu + mouse_event.type = WebInputEvent::MouseUp; + webview->handleInputEvent(mouse_event); + + EXPECT_EQ(1U, test_delegate->captured_context_menu_events().size()); +} diff --git a/webkit/glue/cpp_binding_example.cc b/webkit/glue/cpp_binding_example.cc new file mode 100644 index 0000000..79e96e4 --- /dev/null +++ b/webkit/glue/cpp_binding_example.cc @@ -0,0 +1,123 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains the definition for CppBindingExample, which is used in +// cpp_bound_class_unittest. + +#include "cpp_binding_example.h" + +namespace { + +class PropertyCallbackExample : public CppBoundClass::PropertyCallback { + public: + virtual bool GetValue(CppVariant* value) { + value->Set(value_); + return true; + } + + virtual bool SetValue(const CppVariant& value) { + value_.Set(value); + return true; + } + + private: + CppVariant value_; +}; + +} + +CppBindingExample::CppBindingExample() { + // Map properties. It's recommended, but not required, that the JavaScript + // names (used as the keys in this map) match the names of the member + // variables exposed through those names. + BindProperty("my_value", &my_value); + BindProperty("my_other_value", &my_other_value); + + // Bind property with a callback. + BindProperty("my_value_with_callback", new PropertyCallbackExample()); + // Bind property with a getter callback. + BindProperty("same", &CppBindingExample::same); + + // Map methods. See comment above about names. + BindMethod("echoValue", &CppBindingExample::echoValue); + BindMethod("echoType", &CppBindingExample::echoType); + BindMethod("plus", &CppBindingExample::plus); + + // The fallback method is called when a nonexistent method is called on an + // object. If none is specified, calling a nonexistent method causes an + // exception to be thrown and the JavaScript execution is stopped. + BindFallbackMethod(&CppBindingExample::fallbackMethod); + + my_value.Set(10); + my_other_value.Set("Reinitialized!"); +} + +void CppBindingExample::echoValue(const CppArgumentList& args, + CppVariant* result) { + if (args.size() < 1) { + result->SetNull(); + return; + } + result->Set(args[0]); +} + +void CppBindingExample::echoType(const CppArgumentList& args, + CppVariant* result) { + if (args.size() < 1) { + result->SetNull(); + return; + } + // Note that if args[0] is a string, the following assignment implicitly + // makes a copy of that string, which may have an undesirable impact on + // performance. + CppVariant arg1 = args[0]; + if (arg1.isBool()) + result->Set(true); + else if (arg1.isInt32()) + result->Set(7); + else if (arg1.isDouble()) + result->Set(3.14159); + else if (arg1.isString()) + result->Set("Success!"); +} + +void CppBindingExample::plus(const CppArgumentList& args, + CppVariant* result) { + if (args.size() < 2) { + result->SetNull(); + return; + } + + CppVariant arg1 = args[0]; + CppVariant arg2 = args[1]; + + if (!arg1.isNumber() || !arg2.isNumber()) { + result->SetNull(); + return; + } + + // The value of a CppVariant may be read directly from its NPVariant struct. + // (However, it should only be set using one of the Set() functions.) + double sum = 0.; + if (arg1.isDouble()) + sum += arg1.value.doubleValue; + else if (arg1.isInt32()) + sum += arg1.value.intValue; + + if (arg2.isDouble()) + sum += arg2.value.doubleValue; + else if (arg2.isInt32()) + sum += arg2.value.intValue; + + result->Set(sum); +} + +void CppBindingExample::same(CppVariant* result) { + result->Set(42); +} + +void CppBindingExample::fallbackMethod(const CppArgumentList& args, + CppVariant* result) { + printf("Error: unknown JavaScript method invoked.\n"); +} diff --git a/webkit/glue/cpp_binding_example.h b/webkit/glue/cpp_binding_example.h new file mode 100644 index 0000000..a663682 --- /dev/null +++ b/webkit/glue/cpp_binding_example.h @@ -0,0 +1,78 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/* + CppBindingExample class: + This provides an example of how to use the CppBoundClass to create methods + and properties that can be exposed to JavaScript by an appropriately built + embedding client. It is also used by the CppBoundClass unit test. + + Typically, a class intended to be bound to JavaScript will define a + constructor, any methods and properties to be exposed, and optionally a + destructor. An embedding client can then bind the class to a JavaScript + object in a frame's window using the CppBoundClass::BindToJavascript() method, + generally called from the WebView delegate's WindowObjectCleared(). + + Once this class has been bound, say to the name "example", it might be called + from JavaScript in the following way: + + +*/ + +#ifndef CPP_BINDING_EXAMPLE_H__ +#define CPP_BINDING_EXAMPLE_H__ + +#include "webkit/glue/cpp_bound_class.h" + +class CppBindingExample : public CppBoundClass { + public: + // The default constructor initializes the property and method lists needed + // to bind this class to a JS object. + CppBindingExample(); + + // + // These public member variables and methods implement the methods and + // properties that will be exposed to JavaScript. If needed, the class could + // also contain other methods or variables, which will be hidden from JS + // as long as they're not mapped in the property and method lists created in + // the constructor. + // + // The signatures of any methods to be bound must match + // CppBoundClass::Callback. + // + + // Returns the value that was passed in as its first (only) argument. + void echoValue(const CppArgumentList& args, CppVariant* result); + + // Returns a hard-coded value of the same type (bool, number (double), + // string, or null) that was passed in as an argument. + void echoType(const CppArgumentList& args, CppVariant* result); + + // Returns the sum of the (first) two arguments as a double, if they are both + // numbers (integers or doubles). Otherwise returns null. + void plus(const CppArgumentList& args, CppVariant* result); + + // Always returns the same value -- an example of a read-only property. + void same(CppVariant* result); + + // Invoked when a nonexistent method is called on this example object, this + // prints an error message. + void fallbackMethod(const CppArgumentList& args, CppVariant* result); + + // These properties will also be exposed to JavaScript. + CppVariant my_value; + CppVariant my_other_value; +}; + +#endif // CPP_BINDING_EXAMPLE_H__ diff --git a/webkit/glue/cpp_bound_class.cc b/webkit/glue/cpp_bound_class.cc new file mode 100644 index 0000000..09c3f40 --- /dev/null +++ b/webkit/glue/cpp_bound_class.cc @@ -0,0 +1,330 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains definitions for CppBoundClass + +// Here's the control flow of a JS method getting forwarded to a class. +// - Something calls our NPObject with a function like "Invoke". +// - CppNPObject's static invoke() function forwards it to its attached +// CppBoundClass's Invoke() method. +// - CppBoundClass has then overridden Invoke() to look up the function +// name in its internal map of methods, and then calls the appropriate +// method. + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/utf_string_conversions.h" +#include "third_party/WebKit/WebKit/chromium/public/WebBindings.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebString.h" +#include "webkit/glue/cpp_bound_class.h" + +using WebKit::WebBindings; +using WebKit::WebFrame; + +namespace { + +class CppVariantPropertyCallback : public CppBoundClass::PropertyCallback { + public: + CppVariantPropertyCallback(CppVariant* value) : value_(value) { } + + virtual bool GetValue(CppVariant* value) { + value->Set(*value_); + return true; + } + virtual bool SetValue(const CppVariant& value) { + value_->Set(value); + return true; + } + + private: + CppVariant* value_; +}; + +class GetterPropertyCallback : public CppBoundClass::PropertyCallback { +public: + GetterPropertyCallback(CppBoundClass::GetterCallback* callback) + : callback_(callback) { } + + virtual bool GetValue(CppVariant* value) { + callback_->Run(value); + return true; + } + + virtual bool SetValue(const CppVariant& value) { + return false; + } + +private: + scoped_ptr callback_; +}; + +} + +// Our special NPObject type. We extend an NPObject with a pointer to a +// CppBoundClass, which is just a C++ interface that we forward all NPObject +// callbacks to. +struct CppNPObject { + NPObject parent; // This must be the first field in the struct. + CppBoundClass* bound_class; + + // + // All following objects and functions are static, and just used to interface + // with NPObject/NPClass. + // + + // An NPClass associates static functions of CppNPObject with the + // function pointers used by the JS runtime. + static NPClass np_class_; + + // Allocate a new NPObject with the specified class. + static NPObject* allocate(NPP npp, NPClass* aClass); + + // Free an object. + static void deallocate(NPObject* obj); + + // Returns true if the C++ class associated with this NPObject exposes the + // given property. Called by the JS runtime. + static bool hasProperty(NPObject *obj, NPIdentifier ident); + + // Returns true if the C++ class associated with this NPObject exposes the + // given method. Called by the JS runtime. + static bool hasMethod(NPObject *obj, NPIdentifier ident); + + // If the given method is exposed by the C++ class associated with this + // NPObject, invokes it with the given args and returns a result. Otherwise, + // returns "undefined" (in the JavaScript sense). Called by the JS runtime. + static bool invoke(NPObject *obj, NPIdentifier ident, + const NPVariant *args, uint32_t arg_count, + NPVariant *result); + + // If the given property is exposed by the C++ class associated with this + // NPObject, returns its value. Otherwise, returns "undefined" (in the + // JavaScript sense). Called by the JS runtime. + static bool getProperty(NPObject *obj, NPIdentifier ident, + NPVariant *result); + + // If the given property is exposed by the C++ class associated with this + // NPObject, sets its value. Otherwise, does nothing. Called by the JS + // runtime. + static bool setProperty(NPObject *obj, NPIdentifier ident, + const NPVariant *value); +}; + +// Build CppNPObject's static function pointers into an NPClass, for use +// in constructing NPObjects for the C++ classes. +NPClass CppNPObject::np_class_ = { + NP_CLASS_STRUCT_VERSION, + CppNPObject::allocate, + CppNPObject::deallocate, + /* NPInvalidateFunctionPtr */ NULL, + CppNPObject::hasMethod, + CppNPObject::invoke, + /* NPInvokeDefaultFunctionPtr */ NULL, + CppNPObject::hasProperty, + CppNPObject::getProperty, + CppNPObject::setProperty, + /* NPRemovePropertyFunctionPtr */ NULL +}; + +/* static */ NPObject* CppNPObject::allocate(NPP npp, NPClass* aClass) { + CppNPObject* obj = new CppNPObject; + // obj->parent will be initialized by the NPObject code calling this. + obj->bound_class = NULL; + return &obj->parent; +} + +/* static */ void CppNPObject::deallocate(NPObject* np_obj) { + CppNPObject* obj = reinterpret_cast(np_obj); + delete obj; +} + +/* static */ bool CppNPObject::hasMethod(NPObject* np_obj, + NPIdentifier ident) { + CppNPObject* obj = reinterpret_cast(np_obj); + return obj->bound_class->HasMethod(ident); +} + +/* static */ bool CppNPObject::hasProperty(NPObject* np_obj, + NPIdentifier ident) { + CppNPObject* obj = reinterpret_cast(np_obj); + return obj->bound_class->HasProperty(ident); +} + +/* static */ bool CppNPObject::invoke(NPObject* np_obj, NPIdentifier ident, + const NPVariant* args, uint32_t arg_count, + NPVariant* result) { + CppNPObject* obj = reinterpret_cast(np_obj); + return obj->bound_class->Invoke(ident, args, arg_count, result); +} + +/* static */ bool CppNPObject::getProperty(NPObject* np_obj, + NPIdentifier ident, + NPVariant* result) { + CppNPObject* obj = reinterpret_cast(np_obj); + return obj->bound_class->GetProperty(ident, result); +} + +/* static */ bool CppNPObject::setProperty(NPObject* np_obj, + NPIdentifier ident, + const NPVariant* value) { + CppNPObject* obj = reinterpret_cast(np_obj); + return obj->bound_class->SetProperty(ident, value); +} + +CppBoundClass::~CppBoundClass() { + for (MethodList::iterator i = methods_.begin(); i != methods_.end(); ++i) + delete i->second; + + for (PropertyList::iterator i = properties_.begin(); i != properties_.end(); + ++i) { + delete i->second; + } + + // Unregister ourselves if we were bound to a frame. + if (bound_to_frame_) + WebBindings::unregisterObject(NPVARIANT_TO_OBJECT(self_variant_)); +} + +bool CppBoundClass::HasMethod(NPIdentifier ident) const { + return (methods_.find(ident) != methods_.end()); +} + +bool CppBoundClass::HasProperty(NPIdentifier ident) const { + return (properties_.find(ident) != properties_.end()); +} + +bool CppBoundClass::Invoke(NPIdentifier ident, + const NPVariant* args, + size_t arg_count, + NPVariant* result) { + MethodList::const_iterator method = methods_.find(ident); + Callback* callback; + if (method == methods_.end()) { + if (fallback_callback_.get()) { + callback = fallback_callback_.get(); + } else { + VOID_TO_NPVARIANT(*result); + return false; + } + } else { + callback = (*method).second; + } + + // Build a CppArgumentList argument vector from the NPVariants coming in. + CppArgumentList cpp_args(arg_count); + for (size_t i = 0; i < arg_count; i++) + cpp_args[i].Set(args[i]); + + CppVariant cpp_result; + callback->Run(cpp_args, &cpp_result); + + cpp_result.CopyToNPVariant(result); + return true; +} + +bool CppBoundClass::GetProperty(NPIdentifier ident, NPVariant* result) const { + PropertyList::const_iterator callback = properties_.find(ident); + if (callback == properties_.end()) { + VOID_TO_NPVARIANT(*result); + return false; + } + + CppVariant cpp_value; + if (!callback->second->GetValue(&cpp_value)) + return false; + cpp_value.CopyToNPVariant(result); + return true; +} + +bool CppBoundClass::SetProperty(NPIdentifier ident, + const NPVariant* value) { + PropertyList::iterator callback = properties_.find(ident); + if (callback == properties_.end()) + return false; + + CppVariant cpp_value; + cpp_value.Set(*value); + return (*callback).second->SetValue(cpp_value); +} + +void CppBoundClass::BindCallback(const std::string& name, Callback* callback) { + NPIdentifier ident = WebBindings::getStringIdentifier(name.c_str()); + MethodList::iterator old_callback = methods_.find(ident); + if (old_callback != methods_.end()) { + delete old_callback->second; + if (callback == NULL) { + methods_.erase(old_callback); + return; + } + } + + methods_[ident] = callback; +} + +void CppBoundClass::BindGetterCallback(const std::string& name, + GetterCallback* callback) { + PropertyCallback* property_callback = callback == NULL ? + NULL : new GetterPropertyCallback(callback); + + BindProperty(name, property_callback); +} + +void CppBoundClass::BindProperty(const std::string& name, CppVariant* prop) { + PropertyCallback* property_callback = prop == NULL ? + NULL : new CppVariantPropertyCallback(prop); + + BindProperty(name, property_callback); +} + +void CppBoundClass::BindProperty(const std::string& name, + PropertyCallback* callback) { + NPIdentifier ident = WebBindings::getStringIdentifier(name.c_str()); + PropertyList::iterator old_callback = properties_.find(ident); + if (old_callback != properties_.end()) { + delete old_callback->second; + if (callback == NULL) { + properties_.erase(old_callback); + return; + } + } + + properties_[ident] = callback; +} + +bool CppBoundClass::IsMethodRegistered(const std::string& name) const { + NPIdentifier ident = WebBindings::getStringIdentifier(name.c_str()); + MethodList::const_iterator callback = methods_.find(ident); + return (callback != methods_.end()); +} + +CppVariant* CppBoundClass::GetAsCppVariant() { + if (!self_variant_.isObject()) { + // Create an NPObject using our static NPClass. 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. + NPObject* np_obj = WebBindings::createObject(0, &CppNPObject::np_class_); + CppNPObject* obj = reinterpret_cast(np_obj); + obj->bound_class = this; + self_variant_.Set(np_obj); + WebBindings::releaseObject(np_obj); // CppVariant takes the reference. + } + DCHECK(self_variant_.isObject()); + return &self_variant_; +} + +void CppBoundClass::BindToJavascript(WebFrame* frame, + const std::wstring& classname) { +#if WEBKIT_USING_JSC +#error "This is not going to work anymore...but it's not clear what the solution is...or if it's still necessary." + JSC::JSLock lock(false); +#endif + + // BindToWindowObject will take its own reference to the NPObject, and clean + // up after itself. It will also (indirectly) register the object with V8, + // so we must remember this so we can unregister it when we're destroyed. + frame->bindToWindowObject(WideToUTF16Hack(classname), + NPVARIANT_TO_OBJECT(*GetAsCppVariant())); + bound_to_frame_ = true; +} diff --git a/webkit/glue/cpp_bound_class.h b/webkit/glue/cpp_bound_class.h new file mode 100644 index 0000000..dcd5c3e --- /dev/null +++ b/webkit/glue/cpp_bound_class.h @@ -0,0 +1,181 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/* + CppBoundClass class: + This base class serves as a parent for C++ classes designed to be bound to + JavaScript objects. + + Subclasses should define the constructor to build the property and method + lists needed to bind this class to a JS object. They should also declare + and define member variables and methods to be exposed to JS through + that object. + + See cpp_binding_example.{h|cc} for an example. +*/ + +#ifndef WEBKIT_GLUE_CPP_BOUNDCLASS_H__ +#define WEBKIT_GLUE_CPP_BOUNDCLASS_H__ + +#include +#include + +#include "webkit/glue/cpp_variant.h" + +#include "base/callback.h" +#include "base/scoped_ptr.h" + +namespace WebKit { +class WebFrame; +} + +typedef std::vector CppArgumentList; + +// CppBoundClass lets you map Javascript method calls and property accesses +// directly to C++ method calls and CppVariant* variable access. +class CppBoundClass { + public: + class PropertyCallback { + public: + virtual ~PropertyCallback() { } + + // Sets |value| to the value of the property. Returns false in case of + // failure. |value| is always non-NULL. + virtual bool GetValue(CppVariant* value) = 0; + + // sets the property value to |value|. Returns false in case of failure. + virtual bool SetValue(const CppVariant& value) = 0; + }; + + // The constructor should call BindMethod, BindProperty, and + // SetFallbackMethod as needed to set up the methods, properties, and + // fallback method. + CppBoundClass() : bound_to_frame_(false) { } + virtual ~CppBoundClass(); + + // Return a CppVariant representing this class, for use with BindProperty(). + // The variant type is guaranteed to be NPVariantType_Object. + CppVariant* GetAsCppVariant(); + + // Given a WebFrame, BindToJavascript builds the NPObject that will represent + // the class and binds it to the frame's window under the given name. This + // should generally be called from the WebView delegate's + // WindowObjectCleared(). A class so bound will be accessible to JavaScript + // as window.. The owner of the CppBoundObject is responsible for + // keeping the object around while the frame is alive, and for destroying it + // afterwards. + void BindToJavascript( + WebKit::WebFrame* frame, const std::wstring& classname); + + // The type of callbacks. + typedef Callback2::Type Callback; + typedef Callback1::Type GetterCallback; + + // Used by a test. Returns true if a method with name |name| exists, + // regardless of whether a fallback is registered. + bool IsMethodRegistered(const std::string& name) const; + + protected: + // Bind the Javascript method called |name| to the C++ callback |callback|. + void BindCallback(const std::string& name, Callback* callback); + + // A wrapper for BindCallback, to simplify the common case of binding a + // method on the current object. Though not verified here, |method| + // must be a method of this CppBoundClass subclass. + template + void BindMethod(const std::string& name, + void (T::*method)(const CppArgumentList&, CppVariant*)) { + Callback* callback = + NewCallback( + static_cast(this), method); + BindCallback(name, callback); + } + + // Bind Javascript property |name| to the C++ getter callback |callback|. + // This can be used to create read-only properties. + void BindGetterCallback(const std::string& name, GetterCallback* callback); + + // A wrapper for BindGetterCallback, to simplify the common case of binding a + // property on the current object. Though not verified here, |method| + // must be a method of this CppBoundClass subclass. + template + void BindProperty(const std::string& name, void (T::*method)(CppVariant*)) { + GetterCallback* callback = + NewCallback(static_cast(this), method); + BindGetterCallback(name, callback); + } + + // Bind the Javascript property called |name| to a CppVariant |prop|. + void BindProperty(const std::string& name, CppVariant* prop); + + // Bind Javascript property called |name| to a PropertyCallback |callback|. + // CppBoundClass assumes control over the life time of the |callback|. + void BindProperty(const std::string& name, PropertyCallback* callback); + + // Set the fallback callback, which is called when when a callback is + // invoked that isn't bound. + // If it is NULL (its default value), a JavaScript exception is thrown in + // that case (as normally expected). If non NULL, the fallback method is + // invoked and the script continues its execution. + // Passing NULL for |callback| clears out any existing binding. + // It is used for tests and should probably only be used in such cases + // as it may cause unexpected behaviors (a JavaScript object with a + // fallback always returns true when checked for a method's + // existence). + void BindFallbackCallback(Callback* fallback_callback) { + fallback_callback_.reset(fallback_callback); + } + + // A wrapper for BindFallbackCallback, to simplify the common case of + // binding a method on the current object. Though not verified here, + // |method| must be a method of this CppBoundClass subclass. + // Passing NULL for |method| clears out any existing binding. + template + void BindFallbackMethod( + void (T::*method)(const CppArgumentList&, CppVariant*)) { + if (method) { + Callback* callback = + NewCallback( + static_cast(this), method); + BindFallbackCallback(callback); + } else { + BindFallbackCallback(NULL); + } + } + + // Some fields are protected because some tests depend on accessing them, + // but otherwise they should be considered private. + + typedef std::map PropertyList; + typedef std::map MethodList; + // These maps associate names with property and method pointers to be + // exposed to JavaScript. + PropertyList properties_; + MethodList methods_; + + // The callback gets invoked when a call is made to an nonexistent method. + scoped_ptr fallback_callback_; + + private: + // NPObject callbacks. + friend struct CppNPObject; + bool HasMethod(NPIdentifier ident) const; + bool Invoke(NPIdentifier ident, const NPVariant* args, size_t arg_count, + NPVariant* result); + bool HasProperty(NPIdentifier ident) const; + bool GetProperty(NPIdentifier ident, NPVariant* result) const; + bool SetProperty(NPIdentifier ident, const NPVariant* value); + + // A lazily-initialized CppVariant representing this class. We retain 1 + // reference to this object, and it is released on deletion. + CppVariant self_variant_; + + // True if our np_object has been bound to a WebFrame, in which case it must + // be unregistered with V8 when we delete it. + bool bound_to_frame_; + + DISALLOW_COPY_AND_ASSIGN(CppBoundClass); +}; + +#endif // CPP_BOUNDCLASS_H__ diff --git a/webkit/glue/cpp_bound_class_unittest.cc b/webkit/glue/cpp_bound_class_unittest.cc new file mode 100644 index 0000000..71a56e6 --- /dev/null +++ b/webkit/glue/cpp_bound_class_unittest.cc @@ -0,0 +1,288 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Tests for CppBoundClass, in conjunction with CppBindingExample. Binds +// a CppBindingExample class into JavaScript in a custom test shell and tests +// the binding from the outside by loading JS into the shell. + +#include + +#include "base/message_loop.h" +#include "third_party/WebKit/WebKit/chromium/public/WebData.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURL.h" +#include "third_party/WebKit/WebKit/chromium/public/WebView.h" +#include "webkit/glue/cpp_binding_example.h" +#include "webkit/glue/webkit_glue.h" +#include "webkit/tools/test_shell/test_shell_test.h" + +using WebKit::WebFrame; + +namespace { + +class CppBindingExampleSubObject : public CppBindingExample { + public: + CppBindingExampleSubObject() { + sub_value_.Set("sub!"); + BindProperty("sub_value", &sub_value_); + } + private: + CppVariant sub_value_; +}; + + +class CppBindingExampleWithOptionalFallback : public CppBindingExample { + public: + CppBindingExampleWithOptionalFallback() { + BindProperty("sub_object", sub_object_.GetAsCppVariant()); + } + + void set_fallback_method_enabled(bool state) { + BindFallbackMethod(state ? + &CppBindingExampleWithOptionalFallback::fallbackMethod + : NULL); + } + + // The fallback method does nothing, but because of it the JavaScript keeps + // running when a nonexistent method is called on an object. + void fallbackMethod(const CppArgumentList& args, CppVariant* result) { + } + + private: + CppBindingExampleSubObject sub_object_; +}; + +class ExampleTestShell : public TestShell { + public: + + ExampleTestShell(bool use_fallback_method) { + example_bound_class_.set_fallback_method_enabled(use_fallback_method); + } + + // When called by WebViewDelegate::WindowObjectCleared method, this binds a + // CppExampleObject to window.example. + virtual void BindJSObjectsToWindow(WebFrame* frame) { + example_bound_class_.BindToJavascript(frame, L"example"); + // We use the layoutTestController binding for notifyDone. + TestShell::BindJSObjectsToWindow(frame); + } + + // This is a public interface to TestShell's protected method, so it + // can be called by our CreateEmptyWindow. + bool PublicInitialize(const std::string& starting_url) { + return Initialize(GURL(starting_url)); + } + + CppBindingExampleWithOptionalFallback example_bound_class_; +}; + +class CppBoundClassTest : public TestShellTest { + protected: + // Adapted from TestShell::CreateNewWindow, this creates an + // ExampleTestShellWindow rather than a regular TestShell. + virtual void CreateEmptyWindow() { + ExampleTestShell* host = new ExampleTestShell(useFallback()); + ASSERT_TRUE(host != NULL); + bool rv = host->PublicInitialize("about:blank"); + if (rv) { + test_shell_ = host; + TestShell::windowList()->push_back(host->mainWnd()); + webframe_ = test_shell_->webView()->mainFrame(); + ASSERT_TRUE(webframe_ != NULL); + } else { + delete host; + } + } + + // Wraps the given JavaScript snippet in "); + // The base URL doesn't matter. + webframe_->loadHTMLString(html, GURL("about:blank")); + + test_shell_->WaitTestFinished(); + } + + // Executes the specified JavaScript and checks to be sure that the resulting + // document text is exactly "SUCCESS". + void CheckJavaScriptSuccess(const std::string& javascript) { + ExecuteJavaScript(javascript); + EXPECT_EQ(L"SUCCESS", webkit_glue::DumpDocumentText(webframe_)); + } + + // Executes the specified JavaScript and checks that the resulting document + // text is empty. + void CheckJavaScriptFailure(const std::string& javascript) { + ExecuteJavaScript(javascript); + EXPECT_EQ(L"", webkit_glue::DumpDocumentText(webframe_)); + } + + // Constructs a JavaScript snippet that evaluates and compares the left and + // right expressions, printing "SUCCESS" to the page if they are equal and + // printing their actual values if they are not. Any strings in the + // expressions should be enclosed in single quotes, and no double quotes + // should appear in either expression (even if escaped). (If a test case + // is added that needs fancier quoting, Json::valueToQuotedString could be + // used here. For now, it's not worth adding the dependency.) + std::string BuildJSCondition(std::string left, std::string right) { + return "var leftval = " + left + ";" + + "var rightval = " + right + ";" + + "if (leftval == rightval) {" + + " document.writeln('SUCCESS');" + + "} else {" + + " document.writeln(\"" + + left + " [\" + leftval + \"] != " + + right + " [\" + rightval + \"]\");" + + "}"; + } + +protected: + virtual bool useFallback() { + return false; + } + +private: + WebFrame* webframe_; +}; + +class CppBoundClassWithFallbackMethodTest : public CppBoundClassTest { +protected: + virtual bool useFallback() { + return true; + } +}; + +// Ensures that the example object has been bound to JS. +TEST_F(CppBoundClassTest, ObjectExists) { + std::string js = BuildJSCondition("typeof window.example", "'object'"); + CheckJavaScriptSuccess(js); + + // An additional check to test our test. + js = BuildJSCondition("typeof window.invalid_object", "'undefined'"); + CheckJavaScriptSuccess(js); +} + +TEST_F(CppBoundClassTest, PropertiesAreInitialized) { + std::string js = BuildJSCondition("example.my_value", "10"); + CheckJavaScriptSuccess(js); + + js = BuildJSCondition("example.my_other_value", "'Reinitialized!'"); + CheckJavaScriptSuccess(js); +} + +TEST_F(CppBoundClassTest, SubOject) { + std::string js = BuildJSCondition("typeof window.example.sub_object", + "'object'"); + CheckJavaScriptSuccess(js); + + js = BuildJSCondition("example.sub_object.sub_value", "'sub!'"); + CheckJavaScriptSuccess(js); +} + +TEST_F(CppBoundClassTest, SetAndGetProperties) { + // The property on the left will be set to the value on the right, then + // checked to make sure it holds that same value. + static const std::string tests[] = { + "example.my_value", "7", + "example.my_value", "'test'", + "example.my_other_value", "3.14", + "example.my_other_value", "false", + "" // Array end marker: insert additional test pairs before this. + }; + + for (int i = 0; tests[i] != ""; i += 2) { + std::string left = tests[i]; + std::string right = tests[i + 1]; + // left = right; + std::string js = left; + js.append(" = "); + js.append(right); + js.append(";"); + js.append(BuildJSCondition(left, right)); + CheckJavaScriptSuccess(js); + } +} + +TEST_F(CppBoundClassTest, SetAndGetPropertiesWithCallbacks) { + // TODO(dglazkov): fix NPObject issues around failing property setters and + // getters and add tests for situations when GetProperty or SetProperty fail. + std::string js = "var result = 'SUCCESS';\n" + "example.my_value_with_callback = 10;\n" + "if (example.my_value_with_callback != 10)\n" + " result = 'FAIL: unable to set property.';\n" + "example.my_value_with_callback = 11;\n" + "if (example.my_value_with_callback != 11)\n" + " result = 'FAIL: unable to set property again';\n" + "if (example.same != 42)\n" + " result = 'FAIL: same property should always be 42';\n" + "example.same = 24;\n" + "if (example.same != 42)\n" + " result = 'FAIL: same property should always be 42';\n" + "document.writeln(result);\n"; + CheckJavaScriptSuccess(js); +} + +TEST_F(CppBoundClassTest, InvokeMethods) { + // The expression on the left is expected to return the value on the right. + static const std::string tests[] = { + "example.echoValue(true)", "true", + "example.echoValue(13)", "13", + "example.echoValue(2.718)", "2.718", + "example.echoValue('yes')", "'yes'", + "example.echoValue()", "null", // Too few arguments + + "example.echoType(false)", "true", + "example.echoType(19)", "7", + "example.echoType(9.876)", "3.14159", + "example.echoType('test string')", "'Success!'", + "example.echoType()", "null", // Too few arguments + + // Comparing floats that aren't integer-valued is usually problematic due + // to rounding, but exact powers of 2 should also be safe. + "example.plus(2.5, 18.0)", "20.5", + "example.plus(2, 3.25)", "5.25", + "example.plus(2, 3)", "5", + "example.plus()", "null", // Too few arguments + "example.plus(1)", "null", // Too few arguments + "example.plus(1, 'test')", "null", // Wrong argument type + "example.plus('test', 2)", "null", // Wrong argument type + "example.plus('one', 'two')", "null", // Wrong argument type + "" // Array end marker: insert additional test pairs before this. + }; + + for (int i = 0; tests[i] != ""; i+= 2) { + std::string left = tests[i]; + std::string right = tests[i + 1]; + std::string js = BuildJSCondition(left, right); + CheckJavaScriptSuccess(js); + } + + std::string js = "example.my_value = 3.25; example.my_other_value = 1.25;"; + js.append(BuildJSCondition( + "example.plus(example.my_value, example.my_other_value)", "4.5")); + CheckJavaScriptSuccess(js); +} + +// Tests that invoking a nonexistent method with no fallback method stops the +// script's execution +TEST_F(CppBoundClassTest, + InvokeNonexistentMethodNoFallback) { + std::string js = "example.nonExistentMethod();document.writeln('SUCCESS');"; + CheckJavaScriptFailure(js); +} + +// Ensures existent methods can be invoked successfully when the fallback method +// is used +TEST_F(CppBoundClassWithFallbackMethodTest, + InvokeExistentMethodsWithFallback) { + std::string js = BuildJSCondition("example.echoValue(34)", "34"); + CheckJavaScriptSuccess(js); +} + +} // namespace diff --git a/webkit/glue/cpp_variant.cc b/webkit/glue/cpp_variant.cc new file mode 100644 index 0000000..a254669 --- /dev/null +++ b/webkit/glue/cpp_variant.cc @@ -0,0 +1,268 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains definitions for CppVariant. + +#include +#include "third_party/WebKit/WebKit/chromium/public/WebBindings.h" +#include "webkit/glue/cpp_variant.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" + +using WebKit::WebBindings; + +CppVariant::CppVariant() { + type = NPVariantType_Null; +} + +// Note that Set() performs a deep copy, which is necessary to safely +// call FreeData() on the value in the destructor. +CppVariant::CppVariant(const CppVariant& original) { + type = NPVariantType_Null; + Set(original); +} + +// See comment for copy constructor, above. +CppVariant& CppVariant::operator=(const CppVariant& original) { + if (&original != this) + Set(original); + return *this; +} + +CppVariant::~CppVariant() { + FreeData(); +} + +void CppVariant::FreeData() { + WebBindings::releaseVariantValue(this); +} + +bool CppVariant::isEqual(const CppVariant& other) const { + if (type != other.type) + return false; + + switch (type) { + case NPVariantType_Bool: { + return (value.boolValue == other.value.boolValue); + } + case NPVariantType_Int32: { + return (value.intValue == other.value.intValue); + } + case NPVariantType_Double: { + return (value.doubleValue == other.value.doubleValue); + } + case NPVariantType_String: { + const NPString *this_value = &value.stringValue; + const NPString *other_value = &other.value.stringValue; + uint32_t len = this_value->UTF8Length; + return (len == other_value->UTF8Length && + !strncmp(this_value->UTF8Characters, other_value->UTF8Characters, + len)); + } + case NPVariantType_Null: + case NPVariantType_Void: { + return true; + } + case NPVariantType_Object: { + NPObject *this_value = value.objectValue; + NPObject *other_value = other.value.objectValue; + return (this_value->_class == other_value->_class && + this_value->referenceCount == other_value->referenceCount); + } + } + return false; +} + +void CppVariant::CopyToNPVariant(NPVariant* result) const { + result->type = type; + switch (type) { + case NPVariantType_Bool: + result->value.boolValue = value.boolValue; + break; + case NPVariantType_Int32: + result->value.intValue = value.intValue; + break; + case NPVariantType_Double: + result->value.doubleValue = value.doubleValue; + break; + case NPVariantType_String: + WebBindings::initializeVariantWithStringCopy(result, &value.stringValue); + break; + case NPVariantType_Null: + case NPVariantType_Void: + // Nothing to set. + break; + case NPVariantType_Object: + result->type = NPVariantType_Object; + result->value.objectValue = WebBindings::retainObject(value.objectValue); + break; + } +} + +void CppVariant::Set(const NPVariant& new_value) { + FreeData(); + switch (new_value.type) { + case NPVariantType_Bool: + Set(new_value.value.boolValue); + break; + case NPVariantType_Int32: + Set(new_value.value.intValue); + break; + case NPVariantType_Double: + Set(new_value.value.doubleValue); + break; + case NPVariantType_String: + Set(new_value.value.stringValue); + break; + case NPVariantType_Null: + case NPVariantType_Void: + type = new_value.type; + break; + case NPVariantType_Object: + Set(new_value.value.objectValue); + break; + } +} + +void CppVariant::SetNull() { + FreeData(); + type = NPVariantType_Null; +} + +void CppVariant::Set(bool new_value) { + FreeData(); + type = NPVariantType_Bool; + value.boolValue = new_value; +} + +void CppVariant::Set(int32_t new_value) { + FreeData(); + type = NPVariantType_Int32; + value.intValue = new_value; +} + +void CppVariant::Set(double new_value) { + FreeData(); + type = NPVariantType_Double; + value.doubleValue = new_value; +} + +// The new_value must be a null-terminated string. +void CppVariant::Set(const char* new_value) { + FreeData(); + type = NPVariantType_String; + NPString new_string = {new_value, + static_cast(strlen(new_value))}; + WebBindings::initializeVariantWithStringCopy(this, &new_string); +} + +void CppVariant::Set(const std::string& new_value) { + FreeData(); + type = NPVariantType_String; + NPString new_string = {new_value.data(), + static_cast(new_value.size())}; + WebBindings::initializeVariantWithStringCopy(this, &new_string); +} +void CppVariant::Set(const NPString& new_value) { + FreeData(); + type = NPVariantType_String; + WebBindings::initializeVariantWithStringCopy(this, &new_value); +} + +void CppVariant::Set(NPObject* new_value) { + FreeData(); + type = NPVariantType_Object; + value.objectValue = WebBindings::retainObject(new_value); +} + +std::string CppVariant::ToString() const { + DCHECK(isString()); + return std::string(value.stringValue.UTF8Characters, + value.stringValue.UTF8Length); +} + +int32_t CppVariant::ToInt32() const { + if (isInt32()) { + return value.intValue; + } else if (isDouble()) { + return static_cast(value.doubleValue); + } else { + NOTREACHED(); + return 0; + } +} + +double CppVariant::ToDouble() const { + if (isInt32()) { + return static_cast(value.intValue); + } else if (isDouble()) { + return value.doubleValue; + } else { + NOTREACHED(); + return 0.0; + } +} + +bool CppVariant::ToBoolean() const { + DCHECK(isBool()); + return value.boolValue; +} + +std::vector CppVariant::ToStringVector() const { + + DCHECK(isObject()); + std::vector wstring_vector; + NPObject* np_value = value.objectValue; + NPIdentifier length_id = WebBindings::getStringIdentifier("length"); + + if (WebBindings::hasProperty(NULL, np_value, length_id)) { + NPVariant length_value; + if (WebBindings::getProperty(NULL, np_value, length_id, &length_value)) { + int length = 0; + // The length is a double in some cases. + if (NPVARIANT_IS_DOUBLE(length_value)) + length = static_cast(NPVARIANT_TO_DOUBLE(length_value)); + else if (NPVARIANT_IS_INT32(length_value)) + length = NPVARIANT_TO_INT32(length_value); + WebBindings::releaseVariantValue(&length_value); + + // For sanity, only allow 100 items. + length = std::min(100, length); + for (int i = 0; i < length; ++i) { + // Get each of the items. + std::string index = StringPrintf("%d", i); + NPIdentifier index_id = WebBindings::getStringIdentifier(index.c_str()); + if (WebBindings::hasProperty(NULL, np_value, index_id)) { + NPVariant index_value; + if (WebBindings::getProperty(NULL, np_value, index_id, &index_value)) { + if (NPVARIANT_IS_STRING(index_value)) { + std::string string( + NPVARIANT_TO_STRING(index_value).UTF8Characters, + NPVARIANT_TO_STRING(index_value).UTF8Length); + wstring_vector.push_back(UTF8ToWide(string)); + } + WebBindings::releaseVariantValue(&index_value); + } + } + } + } + } + return wstring_vector; +} + +bool CppVariant::Invoke(const std::string& method, const CppVariant* args, + uint32 arg_count, CppVariant& result) const { + DCHECK(isObject()); + NPIdentifier method_name = WebBindings::getStringIdentifier(method.c_str()); + NPObject* np_object = value.objectValue; + if (WebBindings::hasMethod(NULL, np_object, method_name)) { + NPVariant r; + bool status = WebBindings::invoke(NULL, np_object, method_name, args, arg_count, &r); + result.Set(r); + return status; + } else { + return false; + } +} diff --git a/webkit/glue/cpp_variant.h b/webkit/glue/cpp_variant.h new file mode 100644 index 0000000..71b3166 --- /dev/null +++ b/webkit/glue/cpp_variant.h @@ -0,0 +1,110 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/* + This file contains the declaration for CppVariant, a type used by C++ classes + that are to be bound to JavaScript objects. + + CppVariant exists primarily as an interface between C++ callers and the + corresponding NPVariant type. CppVariant also provides a number of + convenience constructors and accessors, so that the NPVariantType values + don't need to be exposed, and a destructor to free any memory allocated for + string values. + + For a usage example, see cpp_binding_example.{h|cc}. +*/ + +#ifndef WEBKIT_GLUE_CPP_VARIANT_H__ +#define WEBKIT_GLUE_CPP_VARIANT_H__ + +#include +#include + +#include "base/basictypes.h" +#include "third_party/npapi/bindings/npruntime.h" + +class CppVariant : public NPVariant { + public: + CppVariant(); + ~CppVariant(); + void SetNull(); + void Set(bool value); + void Set(int32_t value); + void Set(double value); + + // Note that setting a CppVariant to a string value involves copying the + // string data, which must be freed with a call to FreeData() when the + // CppVariant is set to a different value or is no longer needed. Normally + // this is handled by the other Set() methods and by the destructor. + void Set(const char* value); // Must be a null-terminated string. + void Set(const std::string& value); + void Set(const NPString& new_value); + void Set(const NPVariant& new_value); + + // Note that setting a CppVariant to an NPObject involves ref-counting + // the actual object. FreeData() should only be called if the CppVariant + // is no longer needed. The other Set() methods handle this internally. + // Also, the object's NPClass is expected to be a static object: neither + // the NP runtime nor CPPVariant will ever free it. + void Set(NPObject* new_value); + + // These three methods all perform deep copies of any string data. This + // allows local CppVariants to be released by the destructor without + // corrupting their sources. In performance-critical code, or when strings + // are very long, avoid creating new CppVariants. + // In case of NPObject as the data, the copying involves ref-counting + // as opposed to deep-copying. The ref-counting ensures that sources don't + // get corrupted when the copies get destroyed. + void CopyToNPVariant(NPVariant* result) const; + CppVariant& operator=(const CppVariant& original); + CppVariant(const CppVariant& original); + + // Calls NPN_ReleaseVariantValue, which frees any string data + // held by the object and sets its type to null. + // In case of NPObject, the NPN_ReleaseVariantValue decrements + // the ref-count (releases when ref-count becomes 0) + void FreeData(); + + // Compares this CppVariant's type and value to another's. They must be + // identical in both type and value to be considered equal. For string and + // object types, a deep comparison is performed; that is, the contents of the + // strings, or the classes and refcounts of the objects, must be the same, + // but they need not be the same pointers. + bool isEqual(const CppVariant& other) const; + + // The value of a CppVariant may be read directly from its NPVariant (but + // should only be set using one of the Set() methods above). Although the + // type of a CppVariant is likewise public, it can be accessed through these + // functions rather than directly if a caller wishes to avoid dependence on + // the NPVariantType values. + bool isBool() const { return (type == NPVariantType_Bool); } + bool isInt32() const { return (type == NPVariantType_Int32); } + bool isDouble() const { return (type == NPVariantType_Double); } + bool isNumber() const { return (isInt32() || isDouble()); } + bool isString() const { return (type == NPVariantType_String); } + bool isVoid() const { return (type == NPVariantType_Void); } + bool isNull() const { return (type == NPVariantType_Null); } + bool isEmpty() const { return (isVoid() || isNull()); } + bool isObject() const { return (type == NPVariantType_Object); } + + // Converters. The CppVariant must be of a type convertible to these values. + // For example, ToInteger() works only if isNumber() is true. + std::string ToString() const; + int32_t ToInt32() const; + double ToDouble() const; + bool ToBoolean() const; + // Returns a vector of strings for the specified argument. This is useful + // for converting a JavaScript array of strings into a vector of strings. + std::vector ToStringVector() const; + + // Invoke method of the given name on an object with the supplied arguments. + // The first argument should be the object on which the method is to be + // invoked. Returns whether the method was successfully invoked. If the + // method was invoked successfully, any return value is stored in the + // CppVariant specified by result. + bool Invoke(const std::string& method, const CppVariant* args, + uint32 arg_count, CppVariant& result) const; +}; + +#endif // WEBKIT_GLUE_CPP_VARIANT_H__ diff --git a/webkit/glue/cpp_variant_unittest.cc b/webkit/glue/cpp_variant_unittest.cc new file mode 100644 index 0000000..43c78de --- /dev/null +++ b/webkit/glue/cpp_variant_unittest.cc @@ -0,0 +1,426 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/compiler_specific.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/WebKit/chromium/public/WebBindings.h" +#include "webkit/glue/cpp_variant.h" + +using WebKit::WebBindings; + +// Creates a std::string from an NPVariant of string type. If the NPVariant +// is not a string, empties the std::string. +void MakeStdString(const NPVariant& np, std::string* std_string) { + if (np.type == NPVariantType_String) { + const char* chars = + reinterpret_cast(np.value.stringValue.UTF8Characters); + (*std_string).assign(chars, np.value.stringValue.UTF8Length); + } else { + (*std_string).clear(); + } +} + +// Verifies that the actual NPVariant is a string and that its value matches +// the expected_str. +void CheckString(const std::string& expected_str, const NPVariant& actual) { + EXPECT_EQ(NPVariantType_String, actual.type); + std::string actual_str; + MakeStdString(actual, &actual_str); + EXPECT_EQ(expected_str, actual_str); +} + +// Verifies that both the actual and the expected NPVariants are strings and +// that their values match. +void CheckString(const NPVariant& expected, const NPVariant& actual) { + EXPECT_EQ(NPVariantType_String, expected.type); + std::string expected_str; + MakeStdString(expected, &expected_str); + CheckString(expected_str, actual); +} + +int g_allocate_call_count = 0; +int g_deallocate_call_count = 0; + +void CheckObject(const NPVariant& actual) { + EXPECT_EQ(NPVariantType_Object, actual.type); + EXPECT_TRUE(actual.value.objectValue); + EXPECT_EQ(1U, actual.value.objectValue->referenceCount); + EXPECT_EQ(1, g_allocate_call_count); + EXPECT_EQ(0, g_deallocate_call_count); +} + +NPObject* MockNPAllocate(NPP npp, NPClass* aClass) { + // This is a mock allocate method that mimics the behavior + // of WebBindings::createObject when allocate() is NULL + + ++g_allocate_call_count; + // Ignore npp and NPClass + return reinterpret_cast(malloc(sizeof(NPObject))); +} + +void MockNPDeallocate(NPObject* npobj) { + // This is a mock deallocate method that mimics the behavior + // of NPN_DeallocateObject when deallocate() is NULL + + ++g_deallocate_call_count; + free(npobj); +} + +static NPClass void_class = { NP_CLASS_STRUCT_VERSION, + MockNPAllocate, + MockNPDeallocate, + 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + +NPObject* MakeVoidObject() { + g_allocate_call_count = 0; + g_deallocate_call_count = 0; + return WebBindings::createObject(NULL, &void_class); +} + +TEST(CppVariantTest, NewVariantHasNullType) { + CppVariant value; + EXPECT_EQ(NPVariantType_Null, value.type); +} + +TEST(CppVariantTest, SetNullSetsType) { + CppVariant value; + value.Set(17); + value.SetNull(); + EXPECT_EQ(NPVariantType_Null, value.type); +} + +TEST(CppVariantTest, CopyConstructorDoesDeepCopy) { + CppVariant source; + source.Set("test string"); + CppVariant dest = source; + EXPECT_EQ(NPVariantType_String, dest.type); + EXPECT_EQ(NPVariantType_String, source.type); + + // Ensure that the string was copied, not just the pointer. + EXPECT_NE(source.value.stringValue.UTF8Characters, + dest.value.stringValue.UTF8Characters); + + CheckString(source, dest); +} + +TEST(CppVariantTest, CopyConstructorIncrementsRefCount) { + CppVariant source; + NPObject *object = MakeVoidObject(); + source.Set(object); + // 2 references so far. + EXPECT_EQ(2U, source.value.objectValue->referenceCount); + + CppVariant dest = source; + EXPECT_EQ(3U, dest.value.objectValue->referenceCount); + EXPECT_EQ(1, g_allocate_call_count); + WebBindings::releaseObject(object); + source.SetNull(); + CheckObject(dest); +} + +TEST(CppVariantTest, AssignmentDoesDeepCopy) { + CppVariant source; + source.Set("test string"); + CppVariant dest; + dest = source; + EXPECT_EQ(NPVariantType_String, dest.type); + EXPECT_EQ(NPVariantType_String, source.type); + + // Ensure that the string was copied, not just the pointer. + EXPECT_NE(source.value.stringValue.UTF8Characters, + dest.value.stringValue.UTF8Characters); + + CheckString(source, dest); +} + +TEST(CppVariantTest, AssignmentIncrementsRefCount) { + CppVariant source; + NPObject *object = MakeVoidObject(); + source.Set(object); + // 2 references so far. + EXPECT_EQ(2U, source.value.objectValue->referenceCount); + + CppVariant dest; + dest = source; + EXPECT_EQ(3U, dest.value.objectValue->referenceCount); + EXPECT_EQ(1, g_allocate_call_count); + + WebBindings::releaseObject(object); + source.SetNull(); + CheckObject(dest); +} + +TEST(CppVariantTest, DestroyingCopyDoesNotCorruptSource) { + CppVariant source; + source.Set("test string"); + std::string before; + MakeStdString(source, &before); + { + CppVariant dest = source; + } + CheckString(before, source); + + NPObject *object = MakeVoidObject(); + source.Set(object); + { + CppVariant dest2 = source; + } + WebBindings::releaseObject(object); + CheckObject(source); +} + +TEST(CppVariantTest, CopiesTypeAndValueToNPVariant) { + NPVariant np; + CppVariant cpp; + + cpp.Set(true); + cpp.CopyToNPVariant(&np); + EXPECT_EQ(cpp.type, np.type); + EXPECT_EQ(cpp.value.boolValue, np.value.boolValue); + WebBindings::releaseVariantValue(&np); + + cpp.Set(17); + cpp.CopyToNPVariant(&np); + EXPECT_EQ(cpp.type, np.type); + EXPECT_EQ(cpp.value.intValue, np.value.intValue); + WebBindings::releaseVariantValue(&np); + + cpp.Set(3.1415); + cpp.CopyToNPVariant(&np); + EXPECT_EQ(cpp.type, np.type); + EXPECT_EQ(cpp.value.doubleValue, np.value.doubleValue); + WebBindings::releaseVariantValue(&np); + + cpp.Set("test value"); + cpp.CopyToNPVariant(&np); + CheckString("test value", np); + WebBindings::releaseVariantValue(&np); + + cpp.SetNull(); + cpp.CopyToNPVariant(&np); + EXPECT_EQ(cpp.type, np.type); + WebBindings::releaseVariantValue(&np); + + NPObject *object = MakeVoidObject(); + cpp.Set(object); + cpp.CopyToNPVariant(&np); + WebBindings::releaseObject(object); + cpp.SetNull(); + CheckObject(np); + WebBindings::releaseVariantValue(&np); +} + +TEST(CppVariantTest, SetsTypeAndValueFromNPVariant) { + NPVariant np; + CppVariant cpp; + + VOID_TO_NPVARIANT(np); + cpp.Set(np); + EXPECT_EQ(np.type, cpp.type); + WebBindings::releaseVariantValue(&np); + + NULL_TO_NPVARIANT(np); + cpp.Set(np); + EXPECT_EQ(np.type, cpp.type); + WebBindings::releaseVariantValue(&np); + + BOOLEAN_TO_NPVARIANT(true, np); + cpp.Set(np); + EXPECT_EQ(np.type, cpp.type); + EXPECT_EQ(np.value.boolValue, cpp.value.boolValue); + WebBindings::releaseVariantValue(&np); + + INT32_TO_NPVARIANT(15, np); + cpp.Set(np); + EXPECT_EQ(np.type, cpp.type); + EXPECT_EQ(np.value.intValue, cpp.value.intValue); + WebBindings::releaseVariantValue(&np); + + DOUBLE_TO_NPVARIANT(2.71828, np); + cpp.Set(np); + EXPECT_EQ(np.type, cpp.type); + EXPECT_EQ(np.value.doubleValue, cpp.value.doubleValue); + WebBindings::releaseVariantValue(&np); + + NPString np_ascii_str = { "1st test value", + static_cast(strlen("1st test value")) }; + WebBindings::initializeVariantWithStringCopy(&np, &np_ascii_str); + cpp.Set(np); + CheckString("1st test value", cpp); + WebBindings::releaseVariantValue(&np); + + // Test characters represented in 2/3/4 bytes in UTF-8 + // Greek alpha, Chinese number 1 (horizontal bar), + // Deseret letter (similar to 'O') + NPString np_intl_str = { "\xce\xb1\xe4\xb8\x80\xf0\x90\x90\x84", + static_cast(strlen( + "\xce\xb1\xe4\xb8\x80\xf0\x90\x90\x84")) }; + WebBindings::initializeVariantWithStringCopy(&np, &np_intl_str); + cpp.Set(np); + CheckString("\xce\xb1\xe4\xb8\x80\xf0\x90\x90\x84", cpp); + WebBindings::releaseVariantValue(&np); + + NPObject *obj = MakeVoidObject(); + OBJECT_TO_NPVARIANT(obj, np); // Doesn't make a copy. + cpp.Set(np); + // Use this or WebBindings::releaseObject but NOT both. + WebBindings::releaseVariantValue(&np); + CheckObject(cpp); +} + +TEST(CppVariantTest, SetsSimpleTypesAndValues) { + CppVariant cpp; + cpp.Set(true); + EXPECT_EQ(NPVariantType_Bool, cpp.type); + EXPECT_EQ(true, cpp.value.boolValue); + + cpp.Set(5); + EXPECT_EQ(NPVariantType_Int32, cpp.type); + EXPECT_EQ(5, cpp.value.intValue); + + cpp.Set(1.234); + EXPECT_EQ(NPVariantType_Double, cpp.type); + EXPECT_EQ(1.234, cpp.value.doubleValue); + + // C string + cpp.Set("1st test string"); + CheckString("1st test string", cpp); + + // std::string + std::string source("std test string"); + cpp.Set(source); + CheckString("std test string", cpp); + + // NPString + NPString np_ascii_str = { "test NPString", + static_cast(strlen("test NPString")) }; + cpp.Set(np_ascii_str); + std::string expected("test NPString"); + CheckString(expected, cpp); + + // Test characters represented in 2/3/4 bytes in UTF-8 + // Greek alpha, Chinese number 1 (horizontal bar), + // Deseret letter (similar to 'O') + NPString np_intl_str = { "\xce\xb1\xe4\xb8\x80\xf0\x90\x90\x84", + static_cast(strlen( + "\xce\xb1\xe4\xb8\x80\xf0\x90\x90\x84")) }; + cpp.Set(np_intl_str); + expected = std::string("\xce\xb1\xe4\xb8\x80\xf0\x90\x90\x84"); + CheckString(expected, cpp); + + NPObject* obj = MakeVoidObject(); + cpp.Set(obj); + WebBindings::releaseObject(obj); + CheckObject(cpp); +} + +TEST(CppVariantTest, FreeDataSetsToVoid) { + CppVariant cpp; + EXPECT_EQ(NPVariantType_Null, cpp.type); + cpp.Set(12); + EXPECT_EQ(NPVariantType_Int32, cpp.type); + cpp.FreeData(); + EXPECT_EQ(NPVariantType_Void, cpp.type); +} + +TEST(CppVariantTest, FreeDataReleasesObject) { + CppVariant cpp; + NPObject* object = MakeVoidObject(); + cpp.Set(object); + EXPECT_EQ(2U, object->referenceCount); + cpp.FreeData(); + EXPECT_EQ(1U, object->referenceCount); + EXPECT_EQ(0, g_deallocate_call_count); + + cpp.Set(object); + WebBindings::releaseObject(object); + EXPECT_EQ(0, g_deallocate_call_count); + cpp.FreeData(); + EXPECT_EQ(1, g_deallocate_call_count); +} + +TEST(CppVariantTest, IsTypeFunctionsWork) { + CppVariant cpp; + // These should not happen in practice, since voids are not supported + // This test must be first since it just clobbers internal data without + // releasing. + VOID_TO_NPVARIANT(cpp); + EXPECT_FALSE(cpp.isBool()); + EXPECT_FALSE(cpp.isInt32()); + EXPECT_FALSE(cpp.isDouble()); + EXPECT_FALSE(cpp.isNumber()); + EXPECT_FALSE(cpp.isString()); + EXPECT_TRUE(cpp.isVoid()); + EXPECT_FALSE(cpp.isNull()); + EXPECT_TRUE(cpp.isEmpty()); + + cpp.Set(true); + EXPECT_TRUE(cpp.isBool()); + EXPECT_FALSE(cpp.isInt32()); + EXPECT_FALSE(cpp.isDouble()); + EXPECT_FALSE(cpp.isNumber()); + EXPECT_FALSE(cpp.isString()); + EXPECT_FALSE(cpp.isVoid()); + EXPECT_FALSE(cpp.isNull()); + EXPECT_FALSE(cpp.isEmpty()); + EXPECT_FALSE(cpp.isObject()); + + cpp.Set(12); + EXPECT_FALSE(cpp.isBool()); + EXPECT_TRUE(cpp.isInt32()); + EXPECT_FALSE(cpp.isDouble()); + EXPECT_TRUE(cpp.isNumber()); + EXPECT_FALSE(cpp.isString()); + EXPECT_FALSE(cpp.isVoid()); + EXPECT_FALSE(cpp.isNull()); + EXPECT_FALSE(cpp.isEmpty()); + EXPECT_FALSE(cpp.isObject()); + + cpp.Set(3.1415); + EXPECT_FALSE(cpp.isBool()); + EXPECT_FALSE(cpp.isInt32()); + EXPECT_TRUE(cpp.isDouble()); + EXPECT_TRUE(cpp.isNumber()); + EXPECT_FALSE(cpp.isString()); + EXPECT_FALSE(cpp.isVoid()); + EXPECT_FALSE(cpp.isNull()); + EXPECT_FALSE(cpp.isEmpty()); + EXPECT_FALSE(cpp.isObject()); + + cpp.Set("a string"); + EXPECT_FALSE(cpp.isBool()); + EXPECT_FALSE(cpp.isInt32()); + EXPECT_FALSE(cpp.isDouble()); + EXPECT_FALSE(cpp.isNumber()); + EXPECT_TRUE(cpp.isString()); + EXPECT_FALSE(cpp.isVoid()); + EXPECT_FALSE(cpp.isNull()); + EXPECT_FALSE(cpp.isEmpty()); + EXPECT_FALSE(cpp.isObject()); + + cpp.SetNull(); + EXPECT_FALSE(cpp.isBool()); + EXPECT_FALSE(cpp.isInt32()); + EXPECT_FALSE(cpp.isDouble()); + EXPECT_FALSE(cpp.isNumber()); + EXPECT_FALSE(cpp.isString()); + EXPECT_FALSE(cpp.isVoid()); + EXPECT_TRUE(cpp.isNull()); + EXPECT_TRUE(cpp.isEmpty()); + EXPECT_FALSE(cpp.isObject()); + + NPObject *obj = MakeVoidObject(); + cpp.Set(obj); + EXPECT_FALSE(cpp.isBool()); + EXPECT_FALSE(cpp.isInt32()); + EXPECT_FALSE(cpp.isDouble()); + EXPECT_FALSE(cpp.isNumber()); + EXPECT_FALSE(cpp.isString()); + EXPECT_FALSE(cpp.isVoid()); + EXPECT_FALSE(cpp.isNull()); + EXPECT_FALSE(cpp.isEmpty()); + EXPECT_TRUE(cpp.isObject()); + WebBindings::releaseObject(obj); + CheckObject(cpp); +} diff --git a/webkit/glue/devtools_message_data.cc b/webkit/glue/devtools_message_data.cc new file mode 100644 index 0000000..a9af3c5 --- /dev/null +++ b/webkit/glue/devtools_message_data.cc @@ -0,0 +1,30 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/glue/devtools_message_data.h" + +#include "third_party/WebKit/WebKit/chromium/public/WebCString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebDevToolsMessageData.h" + +using WebKit::WebDevToolsMessageData; +using WebKit::WebString; +using WebKit::WebVector; + +DevToolsMessageData::DevToolsMessageData(const WebDevToolsMessageData& data) + : class_name(data.className.utf8()), + method_name(data.methodName.utf8()) { + for (size_t i = 0; i < data.arguments.size(); i++) + arguments.push_back(data.arguments[i].utf8()); +} + +WebDevToolsMessageData DevToolsMessageData::ToWebDevToolsMessageData() const { + WebDevToolsMessageData result; + result.className = WebString::fromUTF8(class_name); + result.methodName = WebString::fromUTF8(method_name); + WebVector web_args(arguments.size()); + for (size_t i = 0; i < arguments.size(); i++) + web_args[i] = WebString::fromUTF8(arguments[i]); + result.arguments.swap(web_args); + return result; +} diff --git a/webkit/glue/devtools_message_data.h b/webkit/glue/devtools_message_data.h new file mode 100644 index 0000000..31daa14 --- /dev/null +++ b/webkit/glue/devtools_message_data.h @@ -0,0 +1,25 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_DEVTOOLS_MESSAGE_DATA_H_ +#define WEBKIT_GLUE_DEVTOOLS_MESSAGE_DATA_H_ + +#include +#include + +namespace WebKit { +struct WebDevToolsMessageData; +} + +struct DevToolsMessageData { + DevToolsMessageData() {} + explicit DevToolsMessageData(const WebKit::WebDevToolsMessageData&); + WebKit::WebDevToolsMessageData ToWebDevToolsMessageData() const; + + std::string class_name; + std::string method_name; + std::vector arguments; +}; + +#endif // WEBKIT_GLUE_DEVTOOLS_DEVTOOLS_MESSAGE_DATA_H_ diff --git a/webkit/glue/devtools_strings.grd b/webkit/glue/devtools_strings.grd new file mode 100644 index 0000000..a21baf5 --- /dev/null +++ b/webkit/glue/devtools_strings.grd @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Failed to resolve children: %sAllocation failure + + + Corrupted object: %sbad + + + Failed to resolve scope variables: %sUndefined variable + + + Processing... + + + %d1000 ticks processed + + + <source is not available> + + + <Unknown scope type> + + + Developer Tools - %shttp://www.example.com/ + + + Heap + + + Take heap snapshot. + + + Snapshot %d1 + + + Used %1$s100MB of %2$s200MB (%3$.0f50%%) + + + Constructor + + + Count + + + ± Count + + + ± Size + + + Compared to %sSnapshot 1 + + + Show absolute counts and sizes. + + + Show counts and sizes as percentages. + + + new + + + deleted + + + %s+ >1000%% + + + %$1s+%$2.2f5%% + + + %$1s%$2d + + + %$1s%$2s + + + Objects and Data + + + Code + + + + diff --git a/webkit/glue/dom_operations.cc b/webkit/glue/dom_operations.cc new file mode 100644 index 0000000..82e5e3a --- /dev/null +++ b/webkit/glue/dom_operations.cc @@ -0,0 +1,619 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/compiler_specific.h" +#include "base/string_util.h" +#include "third_party/WebKit/WebKit/chromium/public/WebAnimationController.h" +#include "third_party/WebKit/WebKit/chromium/public/WebDocument.h" +#include "third_party/WebKit/WebKit/chromium/public/WebElement.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFormElement.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebInputElement.h" +#include "third_party/WebKit/WebKit/chromium/public/WebNode.h" +#include "third_party/WebKit/WebKit/chromium/public/WebNodeCollection.h" +#include "third_party/WebKit/WebKit/chromium/public/WebNodeList.h" +#include "third_party/WebKit/WebKit/chromium/public/WebVector.h" +#include "third_party/WebKit/WebKit/chromium/public/WebView.h" +#include "webkit/glue/dom_operations.h" +#include "webkit/glue/form_data.h" +#include "webkit/glue/password_form_dom_manager.h" +#include "webkit/glue/webpasswordautocompletelistener_impl.h" + +using WebKit::WebAnimationController; +using WebKit::WebDocument; +using WebKit::WebElement; +using WebKit::WebFormElement; +using WebKit::WebFrame; +using WebKit::WebInputElement; +using WebKit::WebNode; +using WebKit::WebNodeCollection; +using WebKit::WebNodeList; +using WebKit::WebVector; +using WebKit::WebView; + +namespace { + +// Structure for storage the unique set of all savable resource links for +// making sure that no duplicated resource link in final result. The consumer +// of the SavableResourcesUniqueCheck is responsible for keeping these pointers +// valid for the lifetime of the SavableResourcesUniqueCheck instance. +struct SavableResourcesUniqueCheck { + // Unique set of all sub resource links. + std::set* resources_set; + // Unique set of all frame links. + std::set* frames_set; + // Collection of all frames we go through when getting all savable resource + // links. + std::vector* frames; + + SavableResourcesUniqueCheck() + : resources_set(NULL), + frames_set(NULL), + frames(NULL) {} + + SavableResourcesUniqueCheck(std::set* resources_set, + std::set* frames_set, std::vector* frames) + : resources_set(resources_set), + frames_set(frames_set), + frames(frames) {} +}; + +// Get all savable resource links from current element. One element might +// have more than one resource link. It is possible to have some links +// in one CSS stylesheet. +void GetSavableResourceLinkForElement( + const WebElement& element, + const WebDocument& current_doc, + SavableResourcesUniqueCheck* unique_check, + webkit_glue::SavableResourcesResult* result) { + + // Handle frame and iframe tag. + if (element.hasTagName("iframe") || + element.hasTagName("frame")) { + WebFrame* sub_frame = WebFrame::fromFrameOwnerElement(element); + if (sub_frame) + unique_check->frames->push_back(sub_frame); + return; + } + + // Check whether the node has sub resource URL or not. + WebString value = + webkit_glue::GetSubResourceLinkFromElement(element); + if (value.isNull()) + return; + // Get absolute URL. + GURL u = current_doc.completeURL(value); + // ignore invalid URL + if (!u.is_valid()) + return; + // Ignore those URLs which are not standard protocols. Because FTP + // protocol does no have cache mechanism, we will skip all + // sub-resources if they use FTP protocol. + if (!u.SchemeIs("http") && !u.SchemeIs("https") && !u.SchemeIs("file")) + return; + // Ignore duplicated resource link. + if (!unique_check->resources_set->insert(u).second) + return; + result->resources_list->push_back(u); + // Insert referrer for above new resource link. + result->referrers_list->push_back(GURL()); +} + +// Get all savable resource links from current WebFrameImpl object pointer. +void GetAllSavableResourceLinksForFrame(WebFrame* current_frame, + SavableResourcesUniqueCheck* unique_check, + webkit_glue::SavableResourcesResult* result, + const char** savable_schemes) { + // Get current frame's URL. + GURL current_frame_url = current_frame->url(); + + // If url of current frame is invalid, ignore it. + if (!current_frame_url.is_valid()) + return; + + // If url of current frame is not a savable protocol, ignore it. + bool is_valid_protocol = false; + for (int i = 0; savable_schemes[i] != NULL; ++i) { + if (current_frame_url.SchemeIs(savable_schemes[i])) { + is_valid_protocol = true; + break; + } + } + if (!is_valid_protocol) + return; + + // If find same frame we have recorded, ignore it. + if (!unique_check->frames_set->insert(current_frame_url).second) + return; + + // Get current using document. + WebDocument current_doc = current_frame->document(); + // Go through all descent nodes. + WebNodeCollection all = current_doc.all(); + // Go through all node in this frame. + for (WebNode node = all.firstItem(); !node.isNull(); + node = all.nextItem()) { + // We only save HTML resources. + if (!node.isElementNode()) + continue; + WebElement element = node.to(); + GetSavableResourceLinkForElement(element, + current_doc, + unique_check, + result); + } +} + +} // namespace + +namespace webkit_glue { + +// Map element name to a list of pointers to corresponding elements to simplify +// form filling. +typedef std::map + FormInputElementMap; + +// Utility struct for form lookup and autofill. When we parse the DOM to lookup +// a form, in addition to action and origin URL's we have to compare all +// necessary form elements. To avoid having to look these up again when we want +// to fill the form, the FindFormElements function stores the pointers +// in a FormElements* result, referenced to ensure they are safe to use. +struct FormElements { + WebFormElement form_element; + FormInputElementMap input_elements; + FormElements() { + } +}; + +typedef std::vector FormElementsList; + +// Internal implementation of FillForm API. +static bool FillFormImpl(FormElements* fe, const FormData& data) { + if (!fe->form_element.autoComplete()) + return false; + + std::map data_map; + for (size_t i = 0; i < data.fields.size(); i++) + data_map[data.fields[i].name()] = data.fields[i].value(); + + for (FormInputElementMap::iterator it = fe->input_elements.begin(); + it != fe->input_elements.end(); ++it) { + WebKit::WebInputElement& element = it->second; + if (!element.value().isEmpty()) // Don't overwrite pre-filled values. + continue; + if (element.inputType() == WebInputElement::Password && + (!element.isEnabledFormControl() || element.hasAttribute("readonly"))) { + continue; // Don't fill uneditable password fields. + } + element.setValue(data_map[it->first]); + element.setAutofilled(true); + element.dispatchFormControlChangeEvent(); + } + + return false; +} + +// Helper to search the given form element for the specified input elements +// in |data|, and add results to |result|. +static bool FindFormInputElements(WebFormElement* fe, + const FormData& data, + FormElements* result) { + // Loop through the list of elements we need to find on the form in + // order to autofill it. If we don't find any one of them, abort + // processing this form; it can't be the right one. + for (size_t j = 0; j < data.fields.size(); j++) { + WebVector temp_elements; + fe->getNamedElements(data.fields[j].name(), temp_elements); + if (temp_elements.isEmpty()) { + // We didn't find a required element. This is not the right form. + // Make sure no input elements from a partially matched form + // in this iteration remain in the result set. + // Note: clear will remove a reference from each InputElement. + result->input_elements.clear(); + return false; + } + // This element matched, add it to our temporary result. It's possible + // there are multiple matches, but for purposes of identifying the form + // one suffices and if some function needs to deal with multiple + // matching elements it can get at them through the FormElement*. + // Note: This assignment adds a reference to the InputElement. + result->input_elements[data.fields[j].name()] = + temp_elements[0].to(); + } + return true; +} + +// Helper to locate form elements identified by |data|. +static void FindFormElements(WebView* view, + const FormData& data, + FormElementsList* results) { + DCHECK(view); + DCHECK(results); + WebFrame* main_frame = view->mainFrame(); + if (!main_frame) + return; + + GURL::Replacements rep; + rep.ClearQuery(); + rep.ClearRef(); + + // Loop through each frame. + for (WebFrame* f = main_frame; f; f = f->traverseNext(false)) { + WebDocument doc = f->document(); + if (!doc.isHTMLDocument()) + continue; + + GURL full_origin(f->url()); + if (data.origin != full_origin.ReplaceComponents(rep)) + continue; + + WebVector forms; + f->forms(forms); + + for (size_t i = 0; i < forms.size(); ++i) { + WebFormElement fe = forms[i]; + // Action URL must match. + GURL full_action(f->document().completeURL(fe.action())); + if (data.action != full_action.ReplaceComponents(rep)) + continue; + + scoped_ptr curr_elements(new FormElements); + if (!FindFormInputElements(&fe, data, curr_elements.get())) + continue; + + // We found the right element. + // Note: this assignment adds a reference to |fe|. + curr_elements->form_element = fe; + results->push_back(curr_elements.release()); + } + } +} + +void FillPasswordForm(WebView* view, + const PasswordFormFillData& data) { + FormElementsList forms; + // We own the FormElements* in forms. + FindFormElements(view, data.basic_data, &forms); + FormElementsList::iterator iter; + for (iter = forms.begin(); iter != forms.end(); ++iter) { + scoped_ptr form_elements(*iter); + + // If wait_for_username is true, we don't want to initially fill the form + // until the user types in a valid username. + if (!data.wait_for_username) + FillFormImpl(form_elements.get(), data.basic_data); + + // Attach autocomplete listener to enable selecting alternate logins. + // First, get pointers to username element. + WebInputElement username_element = + form_elements->input_elements[data.basic_data.fields[0].name()]; + + // Get pointer to password element. (We currently only support single + // password forms). + WebInputElement password_element = + form_elements->input_elements[data.basic_data.fields[1].name()]; + + username_element.document().frame()->registerPasswordListener( + username_element, + new WebPasswordAutocompleteListenerImpl( + new WebInputElementDelegate(username_element), + new WebInputElementDelegate(password_element), + data)); + } +} + +WebString GetSubResourceLinkFromElement(const WebElement& element) { + const char* attribute_name = NULL; + if (element.hasTagName("img") || + element.hasTagName("script")) { + attribute_name = "src"; + } else if (element.hasTagName("input")) { + const WebInputElement input = element.toConst(); + if (input.inputType() == WebInputElement::Image) { + attribute_name = "src"; + } + } else if (element.hasTagName("body") || + element.hasTagName("table") || + element.hasTagName("tr") || + element.hasTagName("td")) { + attribute_name = "background"; + } else if (element.hasTagName("blockquote") || + element.hasTagName("q") || + element.hasTagName("del") || + element.hasTagName("ins")) { + attribute_name = "cite"; + } else if (element.hasTagName("link")) { + // If the link element is not linked to css, ignore it. + if (LowerCaseEqualsASCII(element.getAttribute("type"), "text/css")) { + // TODO(jnd): Add support for extracting links of sub-resources which + // are inside style-sheet such as @import, url(), etc. + // See bug: http://b/issue?id=1111667. + attribute_name = "href"; + } + } + if (!attribute_name) + return WebString(); + WebString value = element.getAttribute(WebString::fromUTF8(attribute_name)); + // If value has content and not start with "javascript:" then return it, + // otherwise return NULL. + if (!value.isNull() && !value.isEmpty() && + !StartsWithASCII(value.utf8(), "javascript:", false)) + return value; + + return WebString(); +} + +// Get all savable resource links from current webview, include main +// frame and sub-frame +bool GetAllSavableResourceLinksForCurrentPage(WebView* view, + const GURL& page_url, SavableResourcesResult* result, + const char** savable_schemes) { + WebFrame* main_frame = view->mainFrame(); + if (!main_frame) + return false; + + std::set resources_set; + std::set frames_set; + std::vector frames; + SavableResourcesUniqueCheck unique_check(&resources_set, + &frames_set, + &frames); + + GURL main_page_gurl(main_frame->url()); + + // Make sure we are saving same page between embedder and webkit. + // If page has being navigated, embedder will get three empty vector, + // which will make the saving page job ended. + if (page_url != main_page_gurl) + return true; + + // First, process main frame. + frames.push_back(main_frame); + + // Check all resource in this page, include sub-frame. + for (int i = 0; i < static_cast(frames.size()); ++i) { + // Get current frame's all savable resource links. + GetAllSavableResourceLinksForFrame(frames[i], &unique_check, result, + savable_schemes); + } + + // Since frame's src can also point to sub-resources link, so it is possible + // that some URLs in frames_list are also in resources_list. For those + // URLs, we will remove it from frame_list, only keep them in resources_list. + for (std::set::iterator it = frames_set.begin(); + it != frames_set.end(); ++it) { + // Append unique frame source to savable frame list. + if (resources_set.find(*it) == resources_set.end()) + result->frames_list->push_back(*it); + } + + return true; +} + +// Sizes a single size (the width or height) from a 'sizes' attribute. A size +// matches must match the following regex: [1-9][0-9]*. +static int ParseSingleIconSize(const string16& text) { + // Size must not start with 0, and be between 0 and 9. + if (text.empty() || !(text[0] >= L'1' && text[0] <= L'9')) + return 0; + // Make sure all chars are from 0-9. + for (size_t i = 1; i < text.length(); ++i) { + if (!(text[i] >= L'0' && text[i] <= L'9')) + return 0; + } + int output; + if (!StringToInt(text, &output)) + return 0; + return output; +} + +// Parses an icon size. An icon size must match the following regex: +// [1-9][0-9]*x[1-9][0-9]*. +// If the input couldn't be parsed, a size with a width/height < 0 is returned. +static gfx::Size ParseIconSize(const string16& text) { + std::vector sizes; + SplitStringDontTrim(text, L'x', &sizes); + if (sizes.size() != 2) + return gfx::Size(); + + return gfx::Size(ParseSingleIconSize(sizes[0]), + ParseSingleIconSize(sizes[1])); +} + +bool ParseIconSizes(const string16& text, + std::vector* sizes, + bool* is_any) { + *is_any = false; + std::vector size_strings; + SplitStringAlongWhitespace(text, &size_strings); + for (size_t i = 0; i < size_strings.size(); ++i) { + if (EqualsASCII(size_strings[i], "any")) { + *is_any = true; + } else { + gfx::Size size = ParseIconSize(size_strings[i]); + if (size.width() <= 0 || size.height() <= 0) + return false; // Bogus size. + sizes->push_back(size); + } + } + if (*is_any && !sizes->empty()) { + // If is_any is true, it must occur by itself. + return false; + } + return (*is_any || !sizes->empty()); +} + +static void AddInstallIcon(const WebElement& link, + std::vector* icons) { + WebString href = link.getAttribute("href"); + if (href.isNull() || href.isEmpty()) + return; + + // Get complete url. + GURL url = link.document().completeURL(href); + if (!url.is_valid()) + return; + + if (!link.hasAttribute("sizes")) + return; + + bool is_any = false; + std::vector icon_sizes; + if (!ParseIconSizes(link.getAttribute("sizes"), &icon_sizes, &is_any) || + is_any || + icon_sizes.size() != 1) { + return; + } + WebApplicationInfo::IconInfo icon_info; + icon_info.width = icon_sizes[0].width(); + icon_info.height = icon_sizes[0].height(); + icon_info.url = url; + icons->push_back(icon_info); +} + +void GetApplicationInfo(WebView* view, WebApplicationInfo* app_info) { + WebFrame* main_frame = view->mainFrame(); + if (!main_frame) + return; + + WebDocument doc = main_frame->document(); + if (doc.isNull()) + return; + + WebElement head = main_frame->document().head(); + if (head.isNull()) + return; + + WebNodeList children = head.childNodes(); + for (unsigned i = 0; i < children.length(); ++i) { + WebNode child = children.item(i); + if (!child.isElementNode()) + continue; + WebElement elem = child.to(); + + if (elem.hasTagName("link")) { + std::string rel = elem.getAttribute("rel").utf8(); + // "rel" attribute may use either "icon" or "shortcut icon". + // see also + // + // + if (LowerCaseEqualsASCII(rel, "icon") || + LowerCaseEqualsASCII(rel, "shortcut icon")) + AddInstallIcon(elem, &app_info->icons); + } else if (elem.hasTagName("meta") && elem.hasAttribute("name")) { + std::string name = elem.getAttribute("name").utf8(); + WebString content = elem.getAttribute("content"); + if (name == "application-name") { + app_info->title = content; + } else if (name == "description") { + app_info->description = content; + } else if (name == "application-url") { + std::string url = content.utf8(); + GURL main_url = main_frame->url(); + app_info->app_url = main_url.is_valid() ? + main_url.Resolve(url) : GURL(url); + if (!app_info->app_url.is_valid()) + app_info->app_url = GURL(); + } + } + } +} + +bool PauseAnimationAtTimeOnElementWithId(WebView* view, + const std::string& animation_name, + double time, + const std::string& element_id) { + WebFrame* web_frame = view->mainFrame(); + if (!web_frame) + return false; + + WebAnimationController* controller = web_frame->animationController(); + if (!controller) + return false; + + WebElement element = + web_frame->document().getElementById(WebString::fromUTF8(element_id)); + if (element.isNull()) + return false; + return controller->pauseAnimationAtTime(element, + WebString::fromUTF8(animation_name), + time); +} + +bool PauseTransitionAtTimeOnElementWithId(WebView* view, + const std::string& property_name, + double time, + const std::string& element_id) { + WebFrame* web_frame = view->mainFrame(); + if (!web_frame) + return false; + + WebAnimationController* controller = web_frame->animationController(); + if (!controller) + return false; + + WebElement element = + web_frame->document().getElementById(WebString::fromUTF8(element_id)); + if (element.isNull()) + return false; + return controller->pauseTransitionAtTime(element, + WebString::fromUTF8(property_name), + time); +} + +bool ElementDoesAutoCompleteForElementWithId(WebView* view, + const std::string& element_id) { + WebFrame* web_frame = view->mainFrame(); + if (!web_frame) + return false; + + WebElement element = web_frame->document().getElementById( + WebString::fromUTF8(element_id)); + if (element.isNull() || !element.hasTagName("input")) + return false; + + WebInputElement input_element = element.to(); + return input_element.autoComplete(); +} + +int NumberOfActiveAnimations(WebView* view) { + WebFrame* web_frame = view->mainFrame(); + if (!web_frame) + return -1; + + WebAnimationController* controller = web_frame->animationController(); + if (!controller) + return -1; + + return controller->numberOfActiveAnimations(); +} + +void GetMetaElementsWithName(WebDocument* document, + const string16& name, + std::vector* meta_elements) { + DCHECK(document); + DCHECK(meta_elements); + meta_elements->clear(); + WebElement head = document->head(); + if (head.isNull() || !head.hasChildNodes()) + return; + + WebNodeList children = head.childNodes(); + for (size_t i = 0; i < children.length(); ++i) { + WebNode node = children.item(i); + if (!node.isElementNode()) + continue; + WebElement element = node.to(); + if (!element.hasTagName("meta")) + continue; + WebString meta_name = element.getAttribute("name"); + if (meta_name.isNull() || meta_name != name) + continue; + meta_elements->push_back(element); + } +} + +} // webkit_glue diff --git a/webkit/glue/dom_operations.h b/webkit/glue/dom_operations.h new file mode 100644 index 0000000..664add0 --- /dev/null +++ b/webkit/glue/dom_operations.h @@ -0,0 +1,144 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_DOM_OPERATIONS_H__ +#define WEBKIT_GLUE_DOM_OPERATIONS_H__ + +#include +#include +#include + +#include "gfx/size.h" +#include "googleurl/src/gurl.h" + +namespace WebKit { +class WebDocument; +class WebElement; +class WebString; +class WebView; +} + +// A collection of operations that access the underlying WebKit DOM directly. +namespace webkit_glue { + +struct PasswordFormFillData; + +// Fill matching password forms and trigger autocomplete in the case of multiple +// matching logins. +void FillPasswordForm(WebKit::WebView* view, + const PasswordFormFillData& data); + +// Structure for storage the result of getting all savable resource links +// for current page. The consumer of the SavableResourcesResult is responsible +// for keeping these pointers valid for the lifetime of the +// SavableResourcesResult instance. +struct SavableResourcesResult { + // vector which contains all savable links of sub resource. + std::vector* resources_list; + // vector which contains corresponding all referral links of sub resource, + // it matched with links one by one. + std::vector* referrers_list; + // vector which contains all savable links of main frame and sub frames. + std::vector* frames_list; + + // Constructor. + SavableResourcesResult(std::vector* resources_list, + std::vector* referrers_list, + std::vector* frames_list) + : resources_list(resources_list), + referrers_list(referrers_list), + frames_list(frames_list) { } + + private: + DISALLOW_COPY_AND_ASSIGN(SavableResourcesResult); +}; + +// Get all savable resource links from current webview, include main frame +// and sub-frame. After collecting all savable resource links, this function +// will send those links to embedder. Return value indicates whether we get +// all saved resource links successfully. +bool GetAllSavableResourceLinksForCurrentPage(WebKit::WebView* view, + const GURL& page_url, SavableResourcesResult* savable_resources_result, + const char** savable_schemes); + +// Structure used when installing a web page as an app. Populated via +// GetApplicationInfo. +struct WebApplicationInfo { + struct IconInfo { + GURL url; + int width; + int height; + }; + + // Title of the application. This is set from the meta tag whose name is + // 'application-name'. + string16 title; + + // Description of the application. This is set from the meta tag whose name + // is 'description'. + string16 description; + + // URL for the app. This is set from the meta tag whose name is + // 'application-url'. + GURL app_url; + + // Set of available icons. This is set for all link tags whose rel=icon. Only + // icons that have a non-zero (width and/or height) are added. + std::vector icons; +}; + +// Parses the icon's size attribute as defined in the HTML 5 spec. Returns true +// on success, false on errors. On success either all the sizes specified in +// the attribute are added to sizes, or is_any is set to true. +// +// You shouldn't have a need to invoke this directly, it's public for testing. +bool ParseIconSizes(const string16& text, + std::vector* sizes, + bool* is_any); + +// Gets the application info for the specified page. See the description of +// WebApplicationInfo for details as to where each field comes from. +void GetApplicationInfo(WebKit::WebView* view, WebApplicationInfo* app_info); + +// Invokes pauseAnimationAtTime on the AnimationController associated with the +// |view|s main frame. +// This is used by test shell. +bool PauseAnimationAtTimeOnElementWithId(WebKit::WebView* view, + const std::string& animation_name, + double time, + const std::string& element_id); + +// Invokes pauseTransitionAtTime on the AnimationController associated with the +// |view|s main frame. +// This is used by test shell. +bool PauseTransitionAtTimeOnElementWithId(WebKit::WebView* view, + const std::string& property_name, + double time, + const std::string& element_id); + +// Returns true if the element with |element_id| as its id has autocomplete +// on. +bool ElementDoesAutoCompleteForElementWithId(WebKit::WebView* view, + const std::string& element_id); + +// Returns the number of animations currently running. +int NumberOfActiveAnimations(WebKit::WebView* view); + +// Returns the value in an elements resource url attribute. For IMG, SCRIPT or +// INPUT TYPE=image, returns the value in "src". For LINK TYPE=text/css, returns +// the value in "href". For BODY, TABLE, TR, TD, returns the value in +// "background". For BLOCKQUOTE, Q, DEL, INS, returns the value in "cite" +// attribute. Otherwise returns a null WebString. +WebKit::WebString GetSubResourceLinkFromElement( + const WebKit::WebElement& element); + +// Puts the meta-elements of |document| that have the specified |name| in +// |meta_elements|. +void GetMetaElementsWithName(WebKit::WebDocument* document, + const string16& name, + std::vector* meta_elements); + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_DOM_OPERATIONS_H__ diff --git a/webkit/glue/dom_operations_unittest.cc b/webkit/glue/dom_operations_unittest.cc new file mode 100644 index 0000000..e9f590c --- /dev/null +++ b/webkit/glue/dom_operations_unittest.cc @@ -0,0 +1,184 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/string_util.h" +#include "net/base/net_util.h" +#include "net/url_request/url_request_context.h" +#include "third_party/WebKit/WebKit/chromium/public/WebView.h" +#include "webkit/glue/dom_operations.h" +#include "webkit/tools/test_shell/simple_resource_loader_bridge.h" +#include "webkit/tools/test_shell/test_shell_test.h" + +namespace { + +class DomOperationsTests : public TestShellTest { + public: + // Test function GetAllSavableResourceLinksForCurrentPage with a web page. + // We expect result of GetAllSavableResourceLinksForCurrentPage exactly + // matches expected_resources_set. + void GetSavableResourceLinksForPage(const FilePath& page_file_path, + const std::set& expected_resources_set); + + protected: + // testing::Test + virtual void SetUp() { + TestShellTest::SetUp(); + } + + virtual void TearDown() { + TestShellTest::TearDown(); + } +}; + + +void DomOperationsTests::GetSavableResourceLinksForPage( + const FilePath& page_file_path, + const std::set& expected_resources_set) { + // Convert local file path to file URL. + GURL file_url = net::FilePathToFileURL(page_file_path); + // Load the test file. + test_shell_->ResetTestController(); + test_shell_->LoadURL(file_url); + test_shell_->WaitTestFinished(); + // Get all savable resource links for the page. + std::vector resources_list; + std::vector referrers_list; + std::vector frames_list; + webkit_glue::SavableResourcesResult result(&resources_list, + &referrers_list, + &frames_list); + + const char* savable_schemes[] = { + "http", + "https", + "file", + NULL + }; + + ASSERT_TRUE(webkit_glue::GetAllSavableResourceLinksForCurrentPage( + test_shell_->webView(), file_url, &result, savable_schemes)); + // Check all links of sub-resource + for (std::vector::const_iterator cit = resources_list.begin(); + cit != resources_list.end(); ++cit) { + ASSERT_TRUE(expected_resources_set.find(*cit) != + expected_resources_set.end()); + } + // Check all links of frame. + for (std::vector::const_iterator cit = frames_list.begin(); + cit != frames_list.end(); ++cit) { + ASSERT_TRUE(expected_resources_set.find(*cit) != + expected_resources_set.end()); + } +} + +// Test function GetAllSavableResourceLinksForCurrentPage with a web page +// which has valid savable resource links. +TEST_F(DomOperationsTests, GetSavableResourceLinksWithPageHasValidLinks) { + std::set expected_resources_set; + // Set directory of test data. + FilePath page_file_path = data_dir_.AppendASCII("dom_serializer"); + + const char* expected_sub_resource_links[] = { + "file:///c:/yt/css/base_all-vfl36460.css", + "file:///c:/yt/js/base_all_with_bidi-vfl36451.js", + "file:///c:/yt/img/pixel-vfl73.gif" + }; + const char* expected_frame_links[] = { + "youtube_1.htm", + "youtube_2.htm" + }; + // Add all expected links of sub-resource to expected set. + for (size_t i = 0; i < arraysize(expected_sub_resource_links); ++i) + expected_resources_set.insert(GURL(expected_sub_resource_links[i])); + // Add all expected links of frame to expected set. + for (size_t i = 0; i < arraysize(expected_frame_links); ++i) { + const FilePath expected_frame_url = + page_file_path.AppendASCII(expected_frame_links[i]); + expected_resources_set.insert( + net::FilePathToFileURL(expected_frame_url)); + } + + page_file_path = page_file_path.AppendASCII("youtube_1.htm"); + GetSavableResourceLinksForPage(page_file_path, expected_resources_set); +} + +// Test function GetAllSavableResourceLinksForCurrentPage with a web page +// which does not have valid savable resource links. +TEST_F(DomOperationsTests, GetSavableResourceLinksWithPageHasInvalidLinks) { + std::set expected_resources_set; + // Set directory of test data. + FilePath page_file_path = data_dir_.AppendASCII("dom_serializer"); + + const char* expected_frame_links[] = { + "youtube_2.htm" + }; + // Add all expected links of frame to expected set. + for (size_t i = 0; i < arraysize(expected_frame_links); ++i) { + FilePath expected_frame_url = + page_file_path.AppendASCII(expected_frame_links[i]); + expected_resources_set.insert( + net::FilePathToFileURL(expected_frame_url)); + } + + page_file_path = page_file_path.AppendASCII("youtube_2.htm"); + GetSavableResourceLinksForPage(page_file_path, expected_resources_set); +} + +// Tests ParseIconSizes with various input. +TEST_F(DomOperationsTests, ParseIconSizes) { + struct TestData { + const char* input; + const bool expected_result; + const bool is_any; + const size_t expected_size_count; + const int width1; + const int height1; + const int width2; + const int height2; + } data[] = { + // Bogus input cases. + { "10", false, false, 0, 0, 0, 0, 0 }, + { "10 10", false, false, 0, 0, 0, 0, 0 }, + { "010", false, false, 0, 0, 0, 0, 0 }, + { " 010 ", false, false, 0, 0, 0, 0, 0 }, + { " 10x ", false, false, 0, 0, 0, 0, 0 }, + { " x10 ", false, false, 0, 0, 0, 0, 0 }, + { "any 10x10", false, false, 0, 0, 0, 0, 0 }, + { "", false, false, 0, 0, 0, 0, 0 }, + { "10ax11", false, false, 0, 0, 0, 0, 0 }, + + // Any. + { "any", true, true, 0, 0, 0, 0, 0 }, + { " any", true, true, 0, 0, 0, 0, 0 }, + { " any ", true, true, 0, 0, 0, 0, 0 }, + + // Sizes. + { "10x11", true, false, 1, 10, 11, 0, 0 }, + { " 10x11 ", true, false, 1, 10, 11, 0, 0 }, + { " 10x11 1x2", true, false, 2, 10, 11, 1, 2 }, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(data); ++i) { + bool is_any; + std::vector sizes; + bool result = webkit_glue::ParseIconSizes( + ASCIIToUTF16(data[i].input), &sizes, &is_any); + ASSERT_EQ(result, data[i].expected_result); + if (result) { + ASSERT_EQ(data[i].is_any, is_any); + ASSERT_EQ(data[i].expected_size_count, sizes.size()); + if (sizes.size() > 0) { + ASSERT_EQ(data[i].width1, sizes[0].width()); + ASSERT_EQ(data[i].height1, sizes[0].height()); + } + if (sizes.size() > 1) { + ASSERT_EQ(data[i].width2, sizes[1].width()); + ASSERT_EQ(data[i].height2, sizes[1].height()); + } + } + } +} + +} // namespace diff --git a/webkit/glue/dom_serializer_unittest.cc b/webkit/glue/dom_serializer_unittest.cc new file mode 100644 index 0000000..a1846f3 --- /dev/null +++ b/webkit/glue/dom_serializer_unittest.cc @@ -0,0 +1,850 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/compiler_specific.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/hash_tables.h" +#include "base/utf_string_conversions.h" +#include "net/base/net_util.h" +#include "net/url_request/url_request_context.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebData.h" +#include "third_party/WebKit/WebKit/chromium/public/WebDocument.h" +#include "third_party/WebKit/WebKit/chromium/public/WebElement.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebNode.h" +#include "third_party/WebKit/WebKit/chromium/public/WebNodeCollection.h" +#include "third_party/WebKit/WebKit/chromium/public/WebNodeList.h" +#include "third_party/WebKit/WebKit/chromium/public/WebPageSerializer.h" +#include "third_party/WebKit/WebKit/chromium/public/WebPageSerializerClient.h" +#include "third_party/WebKit/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURL.h" +#include "third_party/WebKit/WebKit/chromium/public/WebVector.h" +#include "third_party/WebKit/WebKit/chromium/public/WebView.h" +#include "webkit/glue/dom_operations.h" +#include "webkit/glue/webkit_glue.h" +#include "webkit/tools/test_shell/simple_resource_loader_bridge.h" +#include "webkit/tools/test_shell/test_shell_test.h" + +using WebKit::WebCString; +using WebKit::WebData; +using WebKit::WebDocument; +using WebKit::WebElement; +using WebKit::WebFrame; +using WebKit::WebNode; +using WebKit::WebNodeCollection; +using WebKit::WebNodeList; +using WebKit::WebPageSerializer; +using WebKit::WebPageSerializerClient; +using WebKit::WebNode; +using WebKit::WebString; +using WebKit::WebURL; +using WebKit::WebView; +using WebKit::WebVector; + +namespace { + +// Iterate recursively over sub-frames to find one with with a given url. +WebFrame* FindSubFrameByURL(WebView* web_view, const GURL& url) { + if (!web_view->mainFrame()) + return NULL; + + std::vector stack; + stack.push_back(web_view->mainFrame()); + + while (!stack.empty()) { + WebFrame* current_frame = stack.back(); + stack.pop_back(); + if (GURL(current_frame->url()) == url) + return current_frame; + WebNodeCollection all = current_frame->document().all(); + for (WebNode node = all.firstItem(); + !node.isNull(); node = all.nextItem()) { + if (!node.isElementNode()) + continue; + // Check frame tag and iframe tag + WebElement element = node.to(); + if (!element.hasTagName("frame") && !element.hasTagName("iframe")) + continue; + WebFrame* sub_frame = WebFrame::fromFrameOwnerElement(element); + if (sub_frame) + stack.push_back(sub_frame); + } + } + return NULL; +} + +class DomSerializerTests : public TestShellTest, + public WebPageSerializerClient { + public: + DomSerializerTests() + : local_directory_name_(FILE_PATH_LITERAL("./dummy_files/")) { } + + // DomSerializerDelegate. + void didSerializeDataForFrame(const WebURL& frame_web_url, + const WebCString& data, + PageSerializationStatus status) { + + GURL frame_url(frame_web_url); + // If the all frames are finished saving, check all finish status + if (status == WebPageSerializerClient::AllFramesAreFinished) { + SerializationFinishStatusMap::iterator it = + serialization_finish_status_.begin(); + for (; it != serialization_finish_status_.end(); ++it) + ASSERT_TRUE(it->second); + serialized_ = true; + return; + } + + // Check finish status of current frame. + SerializationFinishStatusMap::iterator it = + serialization_finish_status_.find(frame_url.spec()); + // New frame, set initial status as false. + if (it == serialization_finish_status_.end()) + serialization_finish_status_[frame_url.spec()] = false; + + it = serialization_finish_status_.find(frame_url.spec()); + ASSERT_TRUE(it != serialization_finish_status_.end()); + // In process frame, finish status should be false. + ASSERT_FALSE(it->second); + + // Add data to corresponding frame's content. + serialized_frame_map_[frame_url.spec()] += data.data(); + + // Current frame is completed saving, change the finish status. + if (status == WebPageSerializerClient::CurrentFrameIsFinished) + it->second = true; + } + + bool HasSerializedFrame(const GURL& frame_url) { + return serialized_frame_map_.find(frame_url.spec()) != + serialized_frame_map_.end(); + } + + const std::string& GetSerializedContentForFrame( + const GURL& frame_url) { + return serialized_frame_map_[frame_url.spec()]; + } + + // Load web page according to specific URL. + void LoadPageFromURL(const GURL& page_url) { + // Load the test file. + test_shell_->ResetTestController(); + test_shell_->LoadURL(page_url); + test_shell_->WaitTestFinished(); + } + + // Load web page according to input content and relative URLs within + // the document. + void LoadContents(const std::string& contents, + const GURL& base_url, + const WebString encoding_info) { + test_shell_->ResetTestController(); + // If input encoding is empty, use UTF-8 as default encoding. + if (encoding_info.isEmpty()) { + test_shell_->webView()->mainFrame()->loadHTMLString(contents, base_url); + } else { + WebData data(contents.data(), contents.length()); + + // Do not use WebFrame.LoadHTMLString because it assumes that input + // html contents use UTF-8 encoding. + // TODO(darin): This should use WebFrame::loadData. + WebFrame* web_frame = + test_shell_->webView()->mainFrame(); + + ASSERT_TRUE(web_frame != NULL); + + web_frame->loadData(data, "text/html", encoding_info, base_url); + } + + test_shell_->WaitTestFinished(); + } + + // Serialize page DOM according to specific page URL. The parameter + // recursive_serialization indicates whether we will serialize all + // sub-frames. + void SerializeDomForURL(const GURL& page_url, + bool recursive_serialization) { + // Find corresponding WebFrame according to page_url. + WebFrame* web_frame = FindSubFrameByURL(test_shell_->webView(), + page_url); + ASSERT_TRUE(web_frame != NULL); + // Add input file URl to links_. + links_.assign(&page_url,1); + // Add dummy file path to local_path_. + WebString file_path = webkit_glue::FilePathStringToWebString( + FILE_PATH_LITERAL("c:\\dummy.htm")); + local_paths_.assign(&file_path, 1); + // Start serializing DOM. + bool result = WebPageSerializer::serialize(web_frame, + recursive_serialization, + static_cast(this), + links_, + local_paths_, + webkit_glue::FilePathToWebString(local_directory_name_)); + ASSERT_TRUE(result); + ASSERT_TRUE(serialized_); + } + + private: + // Map frame_url to corresponding serialized_content. + typedef base::hash_map SerializedFrameContentMap; + SerializedFrameContentMap serialized_frame_map_; + // Map frame_url to corresponding status of serialization finish. + typedef base::hash_map SerializationFinishStatusMap; + SerializationFinishStatusMap serialization_finish_status_; + // Flag indicates whether the process of serializing DOM is finished or not. + bool serialized_; + // The links_ contain dummy original URLs of all saved links. + WebVector links_; + // The local_paths_ contain dummy corresponding local file paths of all saved + // links, which matched links_ one by one. + WebVector local_paths_; + // The local_directory_name_ is dummy relative path of directory which + // contain all saved auxiliary files included all sub frames and resources. + const FilePath local_directory_name_; + + protected: + // testing::Test + virtual void SetUp() { + TestShellTest::SetUp(); + serialized_ = false; + } + + virtual void TearDown() { + TestShellTest::TearDown(); + } +}; + +// Helper function that test whether the first node in the doc is a doc type +// node. +bool HasDocType(const WebDocument& doc) { + WebNode node = doc.firstChild(); + if (node.isNull()) + return false; + return node.nodeType() == WebNode::DocumentTypeNode; +} + +// Helper function for checking whether input node is META tag. Return true +// means it is META element, otherwise return false. The parameter charset_info +// return actual charset info if the META tag has charset declaration. +bool IsMetaElement(const WebNode& node, std::string& charset_info) { + if (!node.isElementNode()) + return false; + const WebElement meta = node.toConst(); + if (!meta.hasTagName("meta")) + return false; + charset_info.erase(0, charset_info.length()); + // Check the META charset declaration. + WebString httpEquiv = meta.getAttribute("http-equiv"); + if (LowerCaseEqualsASCII(httpEquiv, "content-type")) { + std::string content = meta.getAttribute("content").utf8(); + int pos = content.find("charset", 0); + if (pos > -1) { + // Add a dummy charset declaration to charset_info, which indicates this + // META tag has charset declaration although we do not get correct value + // yet. + charset_info.append("has-charset-declaration"); + int remaining_length = content.length() - pos - 7; + if (!remaining_length) + return true; + int start_pos = pos + 7; + // Find "=" symbol. + while (remaining_length--) + if (content[start_pos++] == L'=') + break; + // Skip beginning space. + while (remaining_length) { + if (content[start_pos] > 0x0020) + break; + ++start_pos; + --remaining_length; + } + if (!remaining_length) + return true; + int end_pos = start_pos; + // Now we find out the start point of charset info. Search the end point. + while (remaining_length--) { + if (content[end_pos] <= 0x0020 || content[end_pos] == L';') + break; + ++end_pos; + } + // Get actual charset info. + charset_info = content.substr(start_pos, end_pos - start_pos); + return true; + } + } + return true; +} + +// If original contents have document type, the serialized contents also have +// document type. +TEST_F(DomSerializerTests, SerializeHTMLDOMWithDocType) { + FilePath page_file_path = data_dir_; + page_file_path = page_file_path.AppendASCII("dom_serializer"); + page_file_path = page_file_path.AppendASCII("youtube_1.htm"); + GURL file_url = net::FilePathToFileURL(page_file_path); + ASSERT_TRUE(file_url.SchemeIsFile()); + // Load the test file. + LoadPageFromURL(file_url); + // Make sure original contents have document type. + WebFrame* web_frame = FindSubFrameByURL(test_shell_->webView(), file_url); + ASSERT_TRUE(web_frame != NULL); + WebDocument doc = web_frame->document(); + ASSERT_TRUE(HasDocType(doc)); + // Do serialization. + SerializeDomForURL(file_url, false); + // Load the serialized contents. + ASSERT_TRUE(HasSerializedFrame(file_url)); + const std::string& serialized_contents = + GetSerializedContentForFrame(file_url); + LoadContents(serialized_contents, file_url, + web_frame->encoding()); + // Make sure serialized contents still have document type. + web_frame = test_shell_->webView()->mainFrame(); + doc = web_frame->document(); + ASSERT_TRUE(HasDocType(doc)); +} + +// If original contents do not have document type, the serialized contents +// also do not have document type. +TEST_F(DomSerializerTests, SerializeHTMLDOMWithoutDocType) { + FilePath page_file_path = data_dir_; + page_file_path = page_file_path.AppendASCII("dom_serializer"); + page_file_path = page_file_path.AppendASCII("youtube_2.htm"); + GURL file_url = net::FilePathToFileURL(page_file_path); + ASSERT_TRUE(file_url.SchemeIsFile()); + // Load the test file. + LoadPageFromURL(file_url); + // Make sure original contents do not have document type. + WebFrame* web_frame = FindSubFrameByURL(test_shell_->webView(), file_url); + ASSERT_TRUE(web_frame != NULL); + WebDocument doc = web_frame->document(); + ASSERT_TRUE(!HasDocType(doc)); + // Do serialization. + SerializeDomForURL(file_url, false); + // Load the serialized contents. + ASSERT_TRUE(HasSerializedFrame(file_url)); + const std::string& serialized_contents = + GetSerializedContentForFrame(file_url); + LoadContents(serialized_contents, file_url, + web_frame->encoding()); + // Make sure serialized contents do not have document type. + web_frame = test_shell_->webView()->mainFrame(); + doc = web_frame->document(); + ASSERT_TRUE(!HasDocType(doc)); +} + +// Serialize XML document which has all 5 built-in entities. After +// finishing serialization, the serialized contents should be same +// with original XML document. +TEST_F(DomSerializerTests, SerializeXMLDocWithBuiltInEntities) { + FilePath page_file_path = data_dir_; + page_file_path = page_file_path.AppendASCII("dom_serializer"); + page_file_path = page_file_path.AppendASCII("note.xml"); + // Read original contents for later comparison. + std::string original_contents; + ASSERT_TRUE(file_util::ReadFileToString(page_file_path, &original_contents)); + // Get file URL. + GURL file_url = net::FilePathToFileURL(page_file_path); + ASSERT_TRUE(file_url.SchemeIsFile()); + // Load the test file. + LoadPageFromURL(file_url); + // Do serialization. + SerializeDomForURL(file_url, false); + // Compare the serialized contents with original contents. + ASSERT_TRUE(HasSerializedFrame(file_url)); + const std::string& serialized_contents = + GetSerializedContentForFrame(file_url); + ASSERT_EQ(original_contents, serialized_contents); +} + +// When serializing DOM, we add MOTW declaration before html tag. +TEST_F(DomSerializerTests, SerializeHTMLDOMWithAddingMOTW) { + FilePath page_file_path = data_dir_; + page_file_path = page_file_path.AppendASCII("dom_serializer"); + page_file_path = page_file_path.AppendASCII("youtube_2.htm"); + // Read original contents for later comparison . + std::string original_contents; + ASSERT_TRUE(file_util::ReadFileToString(page_file_path, &original_contents)); + // Get file URL. + GURL file_url = net::FilePathToFileURL(page_file_path); + ASSERT_TRUE(file_url.SchemeIsFile()); + // Make sure original contents does not have MOTW; + std::string motw_declaration = + WebPageSerializer::generateMarkOfTheWebDeclaration(file_url).utf8(); + ASSERT_FALSE(motw_declaration.empty()); + // The encoding of original contents is ISO-8859-1, so we convert the MOTW + // declaration to ASCII and search whether original contents has it or not. + ASSERT_TRUE(std::string::npos == + original_contents.find(motw_declaration)); + // Load the test file. + LoadPageFromURL(file_url); + // Do serialization. + SerializeDomForURL(file_url, false); + // Make sure the serialized contents have MOTW ; + ASSERT_TRUE(HasSerializedFrame(file_url)); + const std::string& serialized_contents = + GetSerializedContentForFrame(file_url); + ASSERT_FALSE(std::string::npos == + serialized_contents.find(motw_declaration)); +} + +// When serializing DOM, we will add the META which have correct charset +// declaration as first child of HEAD element for resolving WebKit bug: +// http://bugs.webkit.org/show_bug.cgi?id=16621 even the original document +// does not have META charset declaration. +TEST_F(DomSerializerTests, SerializeHTMLDOMWithNoMetaCharsetInOriginalDoc) { + FilePath page_file_path = data_dir_; + page_file_path = page_file_path.AppendASCII("dom_serializer"); + page_file_path = page_file_path.AppendASCII("youtube_1.htm"); + // Get file URL. + GURL file_url = net::FilePathToFileURL(page_file_path); + ASSERT_TRUE(file_url.SchemeIsFile()); + // Load the test file. + LoadPageFromURL(file_url); + + // Make sure there is no META charset declaration in original document. + WebFrame* web_frame = FindSubFrameByURL(test_shell_->webView(), file_url); + ASSERT_TRUE(web_frame != NULL); + WebDocument doc = web_frame->document(); + ASSERT_TRUE(doc.isHTMLDocument()); + WebElement head_element = doc.head(); + ASSERT_TRUE(!head_element.isNull()); + // Go through all children of HEAD element. + for (WebNode child = head_element.firstChild(); !child.isNull(); + child = child.nextSibling()) { + std::string charset_info; + if (IsMetaElement(child, charset_info)) + ASSERT_TRUE(charset_info.empty()); + } + // Do serialization. + SerializeDomForURL(file_url, false); + + // Load the serialized contents. + ASSERT_TRUE(HasSerializedFrame(file_url)); + const std::string& serialized_contents = + GetSerializedContentForFrame(file_url); + LoadContents(serialized_contents, file_url, + web_frame->encoding()); + // Make sure the first child of HEAD element is META which has charset + // declaration in serialized contents. + web_frame = test_shell_->webView()->mainFrame(); + ASSERT_TRUE(web_frame != NULL); + doc = web_frame->document(); + ASSERT_TRUE(doc.isHTMLDocument()); + head_element = doc.head(); + ASSERT_TRUE(!head_element.isNull()); + WebNode meta_node = head_element.firstChild(); + ASSERT_TRUE(!meta_node.isNull()); + // Get meta charset info. + std::string charset_info2; + ASSERT_TRUE(IsMetaElement(meta_node, charset_info2)); + ASSERT_TRUE(!charset_info2.empty()); + ASSERT_TRUE(charset_info2 == std::string(web_frame->encoding().utf8())); + + // Make sure no more additional META tags which have charset declaration. + for (WebNode child = meta_node.nextSibling(); !child.isNull(); + child = child.nextSibling()) { + std::string charset_info; + if (IsMetaElement(child, charset_info)) + ASSERT_TRUE(charset_info.empty()); + } +} + +// When serializing DOM, if the original document has multiple META charset +// declaration, we will add the META which have correct charset declaration +// as first child of HEAD element and remove all original META charset +// declarations. +TEST_F(DomSerializerTests, + SerializeHTMLDOMWithMultipleMetaCharsetInOriginalDoc) { + FilePath page_file_path = data_dir_; + page_file_path = page_file_path.AppendASCII("dom_serializer"); + page_file_path = page_file_path.AppendASCII("youtube_2.htm"); + // Get file URL. + GURL file_url = net::FilePathToFileURL(page_file_path); + ASSERT_TRUE(file_url.SchemeIsFile()); + // Load the test file. + LoadPageFromURL(file_url); + + // Make sure there are multiple META charset declarations in original + // document. + WebFrame* web_frame = FindSubFrameByURL(test_shell_->webView(), file_url); + ASSERT_TRUE(web_frame != NULL); + WebDocument doc = web_frame->document(); + ASSERT_TRUE(doc.isHTMLDocument()); + WebElement head_ele = doc.head(); + ASSERT_TRUE(!head_ele.isNull()); + // Go through all children of HEAD element. + int charset_declaration_count = 0; + for (WebNode child = head_ele.firstChild(); !child.isNull(); + child = child.nextSibling()) { + std::string charset_info; + if (IsMetaElement(child, charset_info) && !charset_info.empty()) + charset_declaration_count++; + } + // The original doc has more than META tags which have charset declaration. + ASSERT_TRUE(charset_declaration_count > 1); + + // Do serialization. + SerializeDomForURL(file_url, false); + + // Load the serialized contents. + ASSERT_TRUE(HasSerializedFrame(file_url)); + const std::string& serialized_contents = + GetSerializedContentForFrame(file_url); + LoadContents(serialized_contents, file_url, + web_frame->encoding()); + // Make sure only first child of HEAD element is META which has charset + // declaration in serialized contents. + web_frame = test_shell_->webView()->mainFrame(); + ASSERT_TRUE(web_frame != NULL); + doc = web_frame->document(); + ASSERT_TRUE(doc.isHTMLDocument()); + head_ele = doc.head(); + ASSERT_TRUE(!head_ele.isNull()); + WebNode meta_node = head_ele.firstChild(); + ASSERT_TRUE(!meta_node.isNull()); + // Get meta charset info. + std::string charset_info2; + ASSERT_TRUE(IsMetaElement(meta_node, charset_info2)); + ASSERT_TRUE(!charset_info2.empty()); + ASSERT_TRUE(charset_info2 == std::string(web_frame->encoding().utf8())); + + // Make sure no more additional META tags which have charset declaration. + for (WebNode child = meta_node.nextSibling(); !child.isNull(); + child = child.nextSibling()) { + std::string charset_info; + if (IsMetaElement(child, charset_info)) + ASSERT_TRUE(charset_info.empty()); + } +} + +// Test situation of html entities in text when serializing HTML DOM. +TEST_F(DomSerializerTests, SerializeHTMLDOMWithEntitiesInText) { + FilePath page_file_path = data_dir_; + page_file_path = page_file_path.AppendASCII( + "dom_serializer/htmlentities_in_text.htm"); + // Get file URL. The URL is dummy URL to identify the following loading + // actions. The test content is in constant:original_contents. + GURL file_url = net::FilePathToFileURL(page_file_path); + ASSERT_TRUE(file_url.SchemeIsFile()); + // Test contents. + static const char* const original_contents = + "&<>\"\'"; + // Load the test contents. + LoadContents(original_contents, file_url, WebString()); + + // Get BODY's text content in DOM. + WebFrame* web_frame = FindSubFrameByURL(test_shell_->webView(), file_url); + ASSERT_TRUE(web_frame != NULL); + WebDocument doc = web_frame->document(); + ASSERT_TRUE(doc.isHTMLDocument()); + WebElement body_ele = doc.body(); + ASSERT_TRUE(!body_ele.isNull()); + WebNode text_node = body_ele.firstChild(); + ASSERT_TRUE(text_node.isTextNode()); + ASSERT_TRUE(std::string(text_node.createMarkup().utf8()) == + "&<>\"\'"); + // Do serialization. + SerializeDomForURL(file_url, false); + // Compare the serialized contents with original contents. + ASSERT_TRUE(HasSerializedFrame(file_url)); + const std::string& serialized_contents = + GetSerializedContentForFrame(file_url); + // Compare the serialized contents with original contents to make sure + // they are same. + // Because we add MOTW when serializing DOM, so before comparison, we also + // need to add MOTW to original_contents. + std::string original_str = + WebPageSerializer::generateMarkOfTheWebDeclaration(file_url).utf8(); + original_str += original_contents; + // Since WebCore now inserts a new HEAD element if there is no HEAD element + // when creating BODY element. (Please see HTMLParser::bodyCreateErrorCheck.) + // We need to append the HEAD content and corresponding META content if we + // find WebCore-generated HEAD element. + if (!doc.head().isNull()) { + WebString encoding = web_frame->encoding(); + std::string htmlTag(""); + std::string::size_type pos = original_str.find(htmlTag); + ASSERT_NE(std::string::npos, pos); + pos += htmlTag.length(); + std::string head_part(""); + head_part += + WebPageSerializer::generateMetaCharsetDeclaration(encoding).utf8(); + head_part += ""; + original_str.insert(pos, head_part); + } + ASSERT_EQ(original_str, serialized_contents); +} + +// Test situation of html entities in attribute value when serializing +// HTML DOM. +TEST_F(DomSerializerTests, SerializeHTMLDOMWithEntitiesInAttributeValue) { + FilePath page_file_path = data_dir_; + page_file_path = page_file_path.AppendASCII( + "dom_serializer/htmlentities_in_attribute_value.htm"); + // Get file URL. The URL is dummy URL to identify the following loading + // actions. The test content is in constant:original_contents. + GURL file_url = net::FilePathToFileURL(page_file_path); + ASSERT_TRUE(file_url.SchemeIsFile()); + // Test contents. + static const char* const original_contents = + ""; + // Load the test contents. + LoadContents(original_contents, file_url, WebString()); + // Get value of BODY's title attribute in DOM. + WebFrame* web_frame = FindSubFrameByURL(test_shell_->webView(), file_url); + ASSERT_TRUE(web_frame != NULL); + WebDocument doc = web_frame->document(); + ASSERT_TRUE(doc.isHTMLDocument()); + WebElement body_ele = doc.body(); + ASSERT_TRUE(!body_ele.isNull()); + WebString value = body_ele.getAttribute("title"); + ASSERT_TRUE(std::string(value.utf8()) == "&<>\"\'"); + // Do serialization. + SerializeDomForURL(file_url, false); + // Compare the serialized contents with original contents. + ASSERT_TRUE(HasSerializedFrame(file_url)); + const std::string& serialized_contents = + GetSerializedContentForFrame(file_url); + // Compare the serialized contents with original contents to make sure + // they are same. + std::string original_str = + WebPageSerializer::generateMarkOfTheWebDeclaration(file_url).utf8(); + original_str += original_contents; + if (!doc.isNull()) { + WebString encoding = web_frame->encoding(); + std::string htmlTag(""); + std::string::size_type pos = original_str.find(htmlTag); + ASSERT_NE(std::string::npos, pos); + pos += htmlTag.length(); + std::string head_part(""); + head_part += + WebPageSerializer::generateMetaCharsetDeclaration(encoding).utf8(); + head_part += ""; + original_str.insert(pos, head_part); + } + ASSERT_EQ(original_str, serialized_contents); +} + +// Test situation of non-standard HTML entities when serializing HTML DOM. +TEST_F(DomSerializerTests, SerializeHTMLDOMWithNonStandardEntities) { + // Make a test file URL and load it. + FilePath page_file_path = data_dir_; + page_file_path = page_file_path.AppendASCII("dom_serializer"); + page_file_path = page_file_path.AppendASCII("nonstandard_htmlentities.htm"); + GURL file_url = net::FilePathToFileURL(page_file_path); + LoadPageFromURL(file_url); + + // Get value of BODY's title attribute in DOM. + WebFrame* web_frame = FindSubFrameByURL(test_shell_->webView(), file_url); + WebDocument doc = web_frame->document(); + ASSERT_TRUE(doc.isHTMLDocument()); + WebElement body_element = doc.body(); + // Unescaped string for "%⊅&supl;'". + static const wchar_t parsed_value[] = { + '%', 0x2285, 0x00b9, '\'', 0 + }; + WebString value = body_element.getAttribute("title"); + ASSERT_TRUE(UTF16ToWide(value) == parsed_value); + ASSERT_TRUE(UTF16ToWide(body_element.innerText()) == parsed_value); + + // Do serialization. + SerializeDomForURL(file_url, false); + // Check the serialized string. + ASSERT_TRUE(HasSerializedFrame(file_url)); + const std::string& serialized_contents = + GetSerializedContentForFrame(file_url); + // Confirm that the serialized string has no non-standard HTML entities. + ASSERT_EQ(std::string::npos, serialized_contents.find("%")); + ASSERT_EQ(std::string::npos, serialized_contents.find("⊅")); + ASSERT_EQ(std::string::npos, serialized_contents.find("&supl;")); + ASSERT_EQ(std::string::npos, serialized_contents.find("'")); +} + +// Test situation of BASE tag in original document when serializing HTML DOM. +// When serializing, we should comment the BASE tag, append a new BASE tag. +// rewrite all the savable URLs to relative local path, and change other URLs +// to absolute URLs. +TEST_F(DomSerializerTests, SerializeHTMLDOMWithBaseTag) { + // There are total 2 available base tags in this test file. + const int kTotalBaseTagCountInTestFile = 2; + + FilePath page_file_path = data_dir_.AppendASCII("dom_serializer"); + file_util::EnsureEndsWithSeparator(&page_file_path); + + // Get page dir URL which is base URL of this file. + GURL path_dir_url = net::FilePathToFileURL(page_file_path); + // Get file path. + page_file_path = + page_file_path.AppendASCII("html_doc_has_base_tag.htm"); + // Get file URL. + GURL file_url = net::FilePathToFileURL(page_file_path); + ASSERT_TRUE(file_url.SchemeIsFile()); + // Load the test file. + LoadPageFromURL(file_url); + // Since for this test, we assume there is no savable sub-resource links for + // this test file, also all links are relative URLs in this test file, so we + // need to check those relative URLs and make sure document has BASE tag. + WebFrame* web_frame = FindSubFrameByURL(test_shell_->webView(), file_url); + ASSERT_TRUE(web_frame != NULL); + WebDocument doc = web_frame->document(); + ASSERT_TRUE(doc.isHTMLDocument()); + // Go through all descent nodes. + WebNodeCollection all = doc.all(); + int original_base_tag_count = 0; + for (WebNode node = all.firstItem(); !node.isNull(); + node = all.nextItem()) { + if (!node.isElementNode()) + continue; + WebElement element = node.to(); + if (element.hasTagName("base")) { + original_base_tag_count++; + } else { + // Get link. + WebString value = + webkit_glue::GetSubResourceLinkFromElement(element); + if (value.isNull() && element.hasTagName("a")) { + value = element.getAttribute("href"); + if (value.isEmpty()) + value = WebString(); + } + // Each link is relative link. + if (!value.isNull()) { + GURL link(value.utf8()); + ASSERT_TRUE(link.scheme().empty()); + } + } + } + ASSERT_EQ(original_base_tag_count, kTotalBaseTagCountInTestFile); + // Make sure in original document, the base URL is not equal with the + // |path_dir_url|. + GURL original_base_url(doc.baseURL()); + ASSERT_NE(original_base_url, path_dir_url); + + // Do serialization. + SerializeDomForURL(file_url, false); + + // Load the serialized contents. + ASSERT_TRUE(HasSerializedFrame(file_url)); + const std::string& serialized_contents = + GetSerializedContentForFrame(file_url); + LoadContents(serialized_contents, file_url, + web_frame->encoding()); + + // Make sure all links are absolute URLs and doc there are some number of + // BASE tags in serialized HTML data. Each of those BASE tags have same base + // URL which is as same as URL of current test file. + web_frame = test_shell_->webView()->mainFrame(); + ASSERT_TRUE(web_frame != NULL); + doc = web_frame->document(); + ASSERT_TRUE(doc.isHTMLDocument()); + // Go through all descent nodes. + all = doc.all(); + int new_base_tag_count = 0; + for (WebNode node = all.firstItem(); !node.isNull(); + node = all.nextItem()) { + if (!node.isElementNode()) + continue; + WebElement element = node.to(); + if (element.hasTagName("base")) { + new_base_tag_count++; + } else { + // Get link. + WebString value = + webkit_glue::GetSubResourceLinkFromElement(element); + if (value.isNull() && element.hasTagName("a")) { + value = element.getAttribute("href"); + if (value.isEmpty()) + value = WebString(); + } + // Each link is absolute link. + if (!value.isNull()) { + GURL link(std::string(value.utf8())); + ASSERT_FALSE(link.scheme().empty()); + } + } + } + // We have one more added BASE tag which is generated by JavaScript. + ASSERT_EQ(new_base_tag_count, original_base_tag_count + 1); + // Make sure in new document, the base URL is equal with the |path_dir_url|. + GURL new_base_url(doc.baseURL()); + ASSERT_EQ(new_base_url, path_dir_url); +} + +// Serializing page which has an empty HEAD tag. +TEST_F(DomSerializerTests, SerializeHTMLDOMWithEmptyHead) { + FilePath page_file_path = data_dir_; + page_file_path = page_file_path.AppendASCII("dom_serializer"); + page_file_path = page_file_path.AppendASCII("empty_head.htm"); + GURL file_url = net::FilePathToFileURL(page_file_path); + ASSERT_TRUE(file_url.SchemeIsFile()); + + // Load the test html content. + static const char* const empty_head_contents = + "hello world"; + LoadContents(empty_head_contents, file_url, WebString()); + + // Make sure the head tag is empty. + WebFrame* web_frame = test_shell_->webView()->mainFrame(); + ASSERT_TRUE(web_frame != NULL); + WebDocument doc = web_frame->document(); + ASSERT_TRUE(doc.isHTMLDocument()); + WebElement head_element = doc.head(); + ASSERT_TRUE(!head_element.isNull()); + ASSERT_TRUE(!head_element.hasChildNodes()); + ASSERT_TRUE(head_element.childNodes().length() == 0); + + // Do serialization. + SerializeDomForURL(file_url, false); + // Make sure the serialized contents have META ; + ASSERT_TRUE(HasSerializedFrame(file_url)); + const std::string& serialized_contents = + GetSerializedContentForFrame(file_url); + + // Reload serialized contents and make sure there is only one META tag. + LoadContents(serialized_contents, file_url, web_frame->encoding()); + web_frame = test_shell_->webView()->mainFrame(); + ASSERT_TRUE(web_frame != NULL); + doc = web_frame->document(); + ASSERT_TRUE(doc.isHTMLDocument()); + head_element = doc.head(); + ASSERT_TRUE(!head_element.isNull()); + ASSERT_TRUE(head_element.hasChildNodes()); + ASSERT_TRUE(head_element.childNodes().length() == 1); + WebNode meta_node = head_element.firstChild(); + ASSERT_TRUE(!meta_node.isNull()); + // Get meta charset info. + std::string charset_info; + ASSERT_TRUE(IsMetaElement(meta_node, charset_info)); + ASSERT_TRUE(!charset_info.empty()); + ASSERT_TRUE(charset_info == std::string(web_frame->encoding().utf8())); + + // Check the body's first node is text node and its contents are + // "hello world" + WebElement body_element = doc.body(); + ASSERT_TRUE(!body_element.isNull()); + WebNode text_node = body_element.firstChild(); + ASSERT_TRUE(text_node.isTextNode()); + WebString text_node_contents = text_node.nodeValue(); + ASSERT_TRUE(std::string(text_node_contents.utf8()) == "hello world"); +} + +// Test that we don't crash when the page contains an iframe that +// was handled as a download (http://crbug.com/42212). +TEST_F(DomSerializerTests, SerializeDocumentWithDownloadedIFrame) { + FilePath page_file_path = data_dir_; + page_file_path = page_file_path.AppendASCII("dom_serializer"); + page_file_path = page_file_path.AppendASCII("iframe-src-is-exe.htm"); + GURL file_url = net::FilePathToFileURL(page_file_path); + ASSERT_TRUE(file_url.SchemeIsFile()); + // Load the test file. + LoadPageFromURL(file_url); + // Do a recursive serialization. We pass if we don't crash. + SerializeDomForURL(file_url, true); +} + +} // namespace diff --git a/webkit/glue/form_data.h b/webkit/glue/form_data.h new file mode 100644 index 0000000..033cac3 --- /dev/null +++ b/webkit/glue/form_data.h @@ -0,0 +1,41 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_FORM_DATA_H__ +#define WEBKIT_GLUE_FORM_DATA_H__ + +#include + +#include "base/string_util.h" +#include "googleurl/src/gurl.h" +#include "webkit/glue/form_field.h" + +namespace webkit_glue { + +// Holds information about a form to be filled and/or submitted. +struct FormData { + // The name of the form. + string16 name; + // GET or POST. + string16 method; + // The URL (minus query parameters) containing the form. + GURL origin; + // The action target of the form. + GURL action; + // A vector of all the input fields in the form. + std::vector fields; + + // Used by FormStructureTest. + inline bool operator==(const FormData& form) const { + return (name == form.name && + StringToLowerASCII(method) == StringToLowerASCII(form.method) && + origin == form.origin && + action == form.action && + fields == form.fields); + } +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_FORM_DATA_H__ diff --git a/webkit/glue/form_field.cc b/webkit/glue/form_field.cc new file mode 100644 index 0000000..a0fbdef --- /dev/null +++ b/webkit/glue/form_field.cc @@ -0,0 +1,103 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/glue/form_field.h" + +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "third_party/WebKit/WebKit/chromium/public/WebInputElement.h" +#include "third_party/WebKit/WebKit/chromium/public/WebOptionElement.h" +#include "third_party/WebKit/WebKit/chromium/public/WebSelectElement.h" + +using WebKit::WebFormControlElement; +using WebKit::WebElement; +using WebKit::WebInputElement; +using WebKit::WebOptionElement; +using WebKit::WebSelectElement; +using WebKit::WebVector; + +namespace webkit_glue { + +FormField::FormField() + : size_(0) { +} + +// TODO(jhawkins): This constructor should probably be deprecated and the +// functionality moved to FormManager. +FormField::FormField(WebFormControlElement element) + : size_(0) { + name_ = element.nameForAutofill(); + + // TODO(jhawkins): Extract the field label. For now we just use the field + // name. + label_ = name_; + + form_control_type_ = element.formControlType(); + if (form_control_type_ == ASCIIToUTF16("text")) { + const WebInputElement& input_element = element.toConst(); + value_ = input_element.value(); + size_ = input_element.size(); + } else if (form_control_type_ == ASCIIToUTF16("select-one")) { + WebSelectElement select_element = element.to(); + value_ = select_element.value(); + + // For select-one elements copy option strings. + WebVector list_items = select_element.listItems(); + option_strings_.reserve(list_items.size()); + for (size_t i = 0; i < list_items.size(); ++i) { + if (list_items[i].hasTagName("option")) + option_strings_.push_back(list_items[i].to().value()); + } + } + + TrimWhitespace(value_, TRIM_LEADING, &value_); +} + +FormField::FormField(const string16& label, + const string16& name, + const string16& value, + const string16& form_control_type, + int size) + : label_(label), + name_(name), + value_(value), + form_control_type_(form_control_type), + size_(size) { +} + +bool FormField::operator==(const FormField& field) const { + // A FormField stores a value, but the value is not part of the identity of + // the field, so we don't want to compare the values. + return (label_ == field.label_ && + name_ == field.name_ && + form_control_type_ == field.form_control_type_ && + size_ == field.size_); +} + +bool FormField::operator!=(const FormField& field) const { + return !operator==(field); +} + +bool FormField::StrictlyEqualsHack(const FormField& field) const { + return (label_ == field.label_ && + name_ == field.name_ && + value_ == field.value_ && + form_control_type_ == field.form_control_type_ && + size_ == field.size_); +} + +std::ostream& operator<<(std::ostream& os, const FormField& field) { + return os + << UTF16ToUTF8(field.label()) + << " " + << UTF16ToUTF8(field.name()) + << " " + << UTF16ToUTF8(field.value()) + << " " + << UTF16ToUTF8(field.form_control_type()) + << " " + << field.size(); +} + +} // namespace webkit_glue diff --git a/webkit/glue/form_field.h b/webkit/glue/form_field.h new file mode 100644 index 0000000..2a1ffcf --- /dev/null +++ b/webkit/glue/form_field.h @@ -0,0 +1,73 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_FORM_FIELD_H_ +#define WEBKIT_GLUE_FORM_FIELD_H_ + +#include + +#include "base/string16.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFormControlElement.h" + +namespace webkit_glue { + +// Stores information about a field in a form. +class FormField { + public: + FormField(); + explicit FormField(WebKit::WebFormControlElement element); + FormField(const string16& label, + const string16& name, + const string16& value, + const string16& form_control_type, + int size); + + const string16& label() const { return label_; } + const string16& name() const { return name_; } + const string16& value() const { return value_; } + const string16& form_control_type() const { return form_control_type_; } + int size() const { return size_; } + // Returns option string for elements for which they make sense (select-one, + // for example) for the rest of elements return an empty array. + const std::vector& option_strings() const { + return option_strings_; + } + + void set_label(const string16& label) { label_ = label; } + void set_name(const string16& name) { name_ = name; } + void set_value(const string16& value) { value_ = value; } + void set_form_control_type(const string16& form_control_type) { + form_control_type_ = form_control_type; + } + void set_size(int size) { size_ = size; } + void set_option_strings(const std::vector& strings) { + option_strings_ = strings; + } + + // Equality tests for identity which does not include |value_| or |size_|. + // Use |StrictlyEqualsHack| method to test all members. + // TODO(dhollowa): These operators need to be revised when we implement field + // ids. + bool operator==(const FormField& field) const; + bool operator!=(const FormField& field) const; + + // Test equality of all data members. + // TODO(dhollowa): This will be removed when we implement field ids. + bool StrictlyEqualsHack(const FormField& field) const; + + private: + string16 label_; + string16 name_; + string16 value_; + string16 form_control_type_; + int size_; + std::vector option_strings_; +}; + +// So we can compare FormFields with EXPECT_EQ(). +std::ostream& operator<<(std::ostream& os, const FormField& profile); + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_FORM_FIELD_H_ diff --git a/webkit/glue/ftp_directory_listing_response_delegate.cc b/webkit/glue/ftp_directory_listing_response_delegate.cc new file mode 100644 index 0000000..80737db --- /dev/null +++ b/webkit/glue/ftp_directory_listing_response_delegate.cc @@ -0,0 +1,159 @@ +// 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 "webkit/glue/ftp_directory_listing_response_delegate.h" + +#include + +#include "base/i18n/icu_encoding_detection.h" +#include "base/i18n/icu_string_conversions.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "base/sys_string_conversions.h" +#include "base/utf_string_conversions.h" +#include "base/time.h" +#include "net/base/escape.h" +#include "net/base/net_errors.h" +#include "net/base/net_util.h" +#include "net/ftp/ftp_directory_listing_parser.h" +#include "net/ftp/ftp_server_type_histograms.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURL.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLLoaderClient.h" + +using net::FtpDirectoryListingEntry; + +using WebKit::WebURLLoader; +using WebKit::WebURLLoaderClient; +using WebKit::WebURLResponse; + +namespace { + +string16 ConvertPathToUTF16(const std::string& path) { + // Per RFC 2640, FTP servers should use UTF-8 or its proper subset ASCII, + // but many old FTP servers use legacy encodings. Try UTF-8 first. + if (IsStringUTF8(path)) + return UTF8ToUTF16(path); + + // Try detecting the encoding. The sample is rather small though, so it may + // fail. + std::string encoding; + if (base::DetectEncoding(path, &encoding) && !encoding.empty()) { + string16 path_utf16; + if (base::CodepageToUTF16(path, encoding.c_str(), + base::OnStringConversionError::SUBSTITUTE, + &path_utf16)) { + return path_utf16; + } + } + + // Use system native encoding as the last resort. + return WideToUTF16Hack(base::SysNativeMBToWide(path)); +} + +} // namespace + +namespace webkit_glue { + +FtpDirectoryListingResponseDelegate::FtpDirectoryListingResponseDelegate( + WebURLLoaderClient* client, + WebURLLoader* loader, + const WebURLResponse& response) + : client_(client), + loader_(loader), + original_response_(response), + buffer_(base::Time::Now()), + updated_histograms_(false), + had_parsing_error_(false) { + Init(); +} + +void FtpDirectoryListingResponseDelegate::OnReceivedData(const char* data, + int data_len) { + if (had_parsing_error_) + return; + + if (buffer_.ConsumeData(data, data_len) == net::OK) + ProcessReceivedEntries(); + else + had_parsing_error_ = true; +} + +void FtpDirectoryListingResponseDelegate::OnCompletedRequest() { + if (!had_parsing_error_ && buffer_.ProcessRemainingData() == net::OK) + ProcessReceivedEntries(); + else + had_parsing_error_ = true; + + if (had_parsing_error_) + SendDataToClient("\n"); +} + +void FtpDirectoryListingResponseDelegate::Init() { + GURL response_url(original_response_.url()); + UnescapeRule::Type unescape_rules = UnescapeRule::SPACES | + UnescapeRule::URL_SPECIAL_CHARS; + std::string unescaped_path = UnescapeURLComponent(response_url.path(), + unescape_rules); + SendDataToClient(net::GetDirectoryListingHeader( + ConvertPathToUTF16(unescaped_path))); + + // If this isn't top level directory (i.e. the path isn't "/",) + // add a link to the parent directory. + if (response_url.path().length() > 1) { + SendDataToClient(net::GetDirectoryListingEntry( + ASCIIToUTF16(".."), std::string(), false, 0, base::Time())); + } +} + +bool FtpDirectoryListingResponseDelegate::ConvertToServerEncoding( + const string16& filename, std::string* raw_bytes) const { + if (buffer_.encoding().empty()) { + *raw_bytes = std::string(); + return true; + } + + return base::UTF16ToCodepage(filename, buffer_.encoding().c_str(), + base::OnStringConversionError::FAIL, + raw_bytes); +} + +void FtpDirectoryListingResponseDelegate::ProcessReceivedEntries() { + if (!updated_histograms_ && buffer_.EntryAvailable()) { + // Only log the server type if we got enough data to reliably detect it. + net::UpdateFtpServerTypeHistograms(buffer_.GetServerType()); + updated_histograms_ = true; + } + + while (buffer_.EntryAvailable()) { + FtpDirectoryListingEntry entry = buffer_.PopEntry(); + + // Skip the current and parent directory entries in the listing. Our header + // always includes them. + if (EqualsASCII(entry.name, ".") || EqualsASCII(entry.name, "..")) + continue; + + bool is_directory = (entry.type == FtpDirectoryListingEntry::DIRECTORY); + int64 size = entry.size; + if (entry.type != FtpDirectoryListingEntry::FILE) + size = 0; + std::string raw_bytes; + if (ConvertToServerEncoding(entry.name, &raw_bytes)) { + SendDataToClient(net::GetDirectoryListingEntry( + entry.name, raw_bytes, is_directory, size, entry.last_modified)); + } else { + // Consider an encoding problem a non-fatal error. The server's support + // for non-ASCII characters might be buggy. Display an error message, + // but keep trying to display the rest of the listing (most file names + // are ASCII anyway, we could be just unlucky with this one). + had_parsing_error_ = true; + } + } +} + +void FtpDirectoryListingResponseDelegate::SendDataToClient( + const std::string& data) { + client_->didReceiveData(loader_, data.data(), data.length()); +} + +} // namespace webkit_glue diff --git a/webkit/glue/ftp_directory_listing_response_delegate.h b/webkit/glue/ftp_directory_listing_response_delegate.h new file mode 100644 index 0000000..1218da9 --- /dev/null +++ b/webkit/glue/ftp_directory_listing_response_delegate.h @@ -0,0 +1,68 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// A delegate class of WebURLLoaderImpl that handles text/vnd.chromium.ftp-dir +// data. + +#ifndef WEBKIT_GLUE_FTP_DIRECTORY_LISTING_RESPONSE_DELEGATE_H_ +#define WEBKIT_GLUE_FTP_DIRECTORY_LISTING_RESPONSE_DELEGATE_H_ + +#include + +#include "net/ftp/ftp_directory_listing_buffer.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLResponse.h" + +namespace WebKit { +class WebURLLoader; +class WebURLLoaderClient; +} + +namespace webkit_glue { + +class FtpDirectoryListingResponseDelegate { + public: + FtpDirectoryListingResponseDelegate(WebKit::WebURLLoaderClient* client, + WebKit::WebURLLoader* loader, + const WebKit::WebURLResponse& response); + + // Passed through from ResourceHandleInternal + void OnReceivedData(const char* data, int data_len); + void OnCompletedRequest(); + + private: + void Init(); + + // Converts |filename| to detected server encoding and puts the result + // in |raw_bytes| (if no conversion is necessary, an empty string is used). + // Returns true on success. + bool ConvertToServerEncoding(const string16& filename, + std::string* raw_bytes) const; + + // Fetches the listing entries from the buffer and sends them to the client. + void ProcessReceivedEntries(); + + void SendDataToClient(const std::string& data); + + // Pointers to the client and associated loader so we can make callbacks as + // we parse pieces of data. + WebKit::WebURLLoaderClient* client_; + WebKit::WebURLLoader* loader_; + + // The original resource response for this request. We use this as a + // starting point for each parts response. + WebKit::WebURLResponse original_response_; + + // Data buffer also responsible for parsing the listing data. + net::FtpDirectoryListingBuffer buffer_; + + // True if we updated histogram data (we only want to do it once). + bool updated_histograms_; + + // True if we got an error when parsing the response. + bool had_parsing_error_; +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_FTP_DIRECTORY_LISTING_RESPONSE_DELEGATE_H_ diff --git a/webkit/glue/glue_serialize.cc b/webkit/glue/glue_serialize.cc new file mode 100644 index 0000000..caf37b4 --- /dev/null +++ b/webkit/glue/glue_serialize.cc @@ -0,0 +1,473 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/glue/glue_serialize.h" + +#include + +#include "base/pickle.h" +#include "base/utf_string_conversions.h" +#include "googleurl/src/gurl.h" +#include "third_party/WebKit/WebKit/chromium/public/WebData.h" +#include "third_party/WebKit/WebKit/chromium/public/WebHistoryItem.h" +#include "third_party/WebKit/WebKit/chromium/public/WebHTTPBody.h" +#include "third_party/WebKit/WebKit/chromium/public/WebPoint.h" +#include "third_party/WebKit/WebKit/chromium/public/WebSerializedScriptValue.h" +#include "third_party/WebKit/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebVector.h" +#include "webkit/glue/webkit_glue.h" + +using WebKit::WebData; +using WebKit::WebFileInfo; +using WebKit::WebHistoryItem; +using WebKit::WebHTTPBody; +using WebKit::WebPoint; +using WebKit::WebSerializedScriptValue; +using WebKit::WebString; +using WebKit::WebUChar; +using WebKit::WebVector; + +namespace webkit_glue { + +struct SerializeObject { + SerializeObject() : iter(NULL) {} + SerializeObject(const char* data, int len) : pickle(data, len), iter(NULL) {} + + std::string GetAsString() { + return std::string(static_cast(pickle.data()), pickle.size()); + } + + Pickle pickle; + mutable void* iter; + mutable int version; +}; + +// TODO(mpcomplete): obsolete versions 1 and 2 after 1/1/2008. +// Version ID used in reading/writing history items. +// 1: Initial revision. +// 2: Added case for NULL string versus "". Version 2 code can read Version 1 +// data, but not vice versa. +// 3: Version 2 was broken, it stored number of WebUChars, not number of bytes. +// This version checks and reads v1 and v2 correctly. +// 4: Adds support for storing FormData::identifier(). +// 5: Adds support for empty FormData +// 6: Adds support for documentSequenceNumbers +// 7: Adds support for stateObject +// 8: Adds support for file range and modification time +// 9: Adds support for itemSequenceNumbers +// Should be const, but unit tests may modify it. +// +// NOTE: If the version is -1, then the pickle contains only a URL string. +// See CreateHistoryStateForURL. +// +int kVersion = 9; + +// A bunch of convenience functions to read/write to SerializeObjects. +// The serializers assume the input data is in the correct format and so does +// no error checking. +inline void WriteData(const void* data, int length, SerializeObject* obj) { + obj->pickle.WriteData(static_cast(data), length); +} + +inline void ReadData(const SerializeObject* obj, const void** data, + int* length) { + const char* tmp = NULL; + obj->pickle.ReadData(&obj->iter, &tmp, length); + *data = tmp; +} + +inline bool ReadBytes(const SerializeObject* obj, const void** data, + int length) { + const char *tmp; + if (!obj->pickle.ReadBytes(&obj->iter, &tmp, length)) + return false; + *data = tmp; + return true; +} + +inline void WriteInteger(int data, SerializeObject* obj) { + obj->pickle.WriteInt(data); +} + +inline int ReadInteger(const SerializeObject* obj) { + int tmp = 0; + obj->pickle.ReadInt(&obj->iter, &tmp); + return tmp; +} + +inline void WriteInteger64(int64 data, SerializeObject* obj) { + obj->pickle.WriteInt64(data); +} + +inline int64 ReadInteger64(const SerializeObject* obj) { + int64 tmp = 0; + obj->pickle.ReadInt64(&obj->iter, &tmp); + return tmp; +} + +inline void WriteReal(double data, SerializeObject* obj) { + WriteData(&data, sizeof(double), obj); +} + +inline double ReadReal(const SerializeObject* obj) { + const void* tmp; + int length = 0; + ReadData(obj, &tmp, &length); + if (length > 0 && length >= static_cast(sizeof(0.0))) + return *static_cast(tmp); + else + return 0.0; +} + +inline void WriteBoolean(bool data, SerializeObject* obj) { + obj->pickle.WriteInt(data ? 1 : 0); +} + +inline bool ReadBoolean(const SerializeObject* obj) { + bool tmp = false; + obj->pickle.ReadBool(&obj->iter, &tmp); + return tmp; +} + +inline void WriteGURL(const GURL& url, SerializeObject* obj) { + obj->pickle.WriteString(url.possibly_invalid_spec()); +} + +inline GURL ReadGURL(const SerializeObject* obj) { + std::string spec; + obj->pickle.ReadString(&obj->iter, &spec); + return GURL(spec); +} + +// Read/WriteString pickle the WebString as . +// If length == -1, then the WebString itself is NULL (WebString()). +// Otherwise the length is the number of WebUChars (not bytes) in the WebString. +inline void WriteString(const WebString& str, SerializeObject* obj) { + switch (kVersion) { + case 1: + // Version 1 writes . + // It saves WebString() and "" as "". + obj->pickle.WriteInt(str.length() * sizeof(WebUChar)); + obj->pickle.WriteBytes(str.data(), str.length() * sizeof(WebUChar)); + break; + case 2: + // Version 2 writes . + // It uses -1 in the length field to mean WebString(). + if (str.isNull()) { + obj->pickle.WriteInt(-1); + } else { + obj->pickle.WriteInt(str.length()); + obj->pickle.WriteBytes(str.data(), + str.length() * sizeof(WebUChar)); + } + break; + default: + // Version 3+ writes . + // It uses -1 in the length field to mean WebString(). + if (str.isNull()) { + obj->pickle.WriteInt(-1); + } else { + obj->pickle.WriteInt(str.length() * sizeof(WebUChar)); + obj->pickle.WriteBytes(str.data(), + str.length() * sizeof(WebUChar)); + } + break; + } +} + +// This reads a serialized WebString from obj. If a string can't be read, +// WebString() is returned. +inline WebString ReadString(const SerializeObject* obj) { + int length; + + // Versions 1, 2, and 3 all start with an integer. + if (!obj->pickle.ReadInt(&obj->iter, &length)) + return WebString(); + + // Starting with version 2, -1 means WebString(). + if (length == -1) + return WebString(); + + // In version 2, the length field was the length in WebUChars. + // In version 1 and 3 it is the length in bytes. + int bytes = ((obj->version == 2) ? length * sizeof(WebUChar) : length); + + const void* data; + if (!ReadBytes(obj, &data, bytes)) + return WebString(); + return WebString(static_cast(data), + bytes / sizeof(WebUChar)); +} + +// Writes a Vector of Strings into a SerializeObject for serialization. +static void WriteStringVector( + const WebVector& data, SerializeObject* obj) { + WriteInteger(static_cast(data.size()), obj); + for (size_t i = 0, c = data.size(); i < c; ++i) { + unsigned ui = static_cast(i); // sigh + WriteString(data[ui], obj); + } +} + +static WebVector ReadStringVector(const SerializeObject* obj) { + int num_elements = ReadInteger(obj); + WebVector result(static_cast(num_elements)); + for (int i = 0; i < num_elements; ++i) + result[i] = ReadString(obj); + return result; +} + +// Writes a FormData object into a SerializeObject for serialization. +static void WriteFormData(const WebHTTPBody& http_body, SerializeObject* obj) { + WriteBoolean(!http_body.isNull(), obj); + + if (http_body.isNull()) + return; + + WriteInteger(static_cast(http_body.elementCount()), obj); + WebHTTPBody::Element element; + for (size_t i = 0; http_body.elementAt(i, element); ++i) { + WriteInteger(element.type, obj); + if (element.type == WebHTTPBody::Element::TypeData) { + WriteData(element.data.data(), static_cast(element.data.size()), + obj); + } else { + WriteString(element.filePath, obj); + WriteInteger64(element.fileStart, obj); + WriteInteger64(element.fileLength, obj); + WriteReal(element.fileInfo.modificationTime, obj); + } + } + WriteInteger64(http_body.identifier(), obj); +} + +static WebHTTPBody ReadFormData(const SerializeObject* obj) { + // In newer versions, an initial boolean indicates if we have form data. + if (obj->version >= 5 && !ReadBoolean(obj)) + return WebHTTPBody(); + + // In older versions, 0 elements implied no form data. + int num_elements = ReadInteger(obj); + if (num_elements == 0 && obj->version < 5) + return WebHTTPBody(); + + WebHTTPBody http_body; + http_body.initialize(); + + for (int i = 0; i < num_elements; ++i) { + int type = ReadInteger(obj); + if (type == WebHTTPBody::Element::TypeData) { + const void* data; + int length = -1; + ReadData(obj, &data, &length); + if (length >= 0) + http_body.appendData(WebData(static_cast(data), length)); + } else { + WebString file_path = ReadString(obj); + long long file_start = 0; + long long file_length = -1; + WebFileInfo file_info; + if (obj->version >= 8) { + file_start = ReadInteger64(obj); + file_length = ReadInteger64(obj); + file_info.modificationTime = ReadReal(obj); + } + http_body.appendFileRange(file_path, file_start, file_length, file_info); + } + } + if (obj->version >= 4) + http_body.setIdentifier(ReadInteger64(obj)); + + return http_body; +} + +// Writes the HistoryItem data into the SerializeObject object for +// serialization. +static void WriteHistoryItem( + const WebHistoryItem& item, SerializeObject* obj) { + // WARNING: This data may be persisted for later use. As such, care must be + // taken when changing the serialized format. If a new field needs to be + // written, only adding at the end will make it easier to deal with loading + // older versions. Similarly, this should NOT save fields with sensitive + // data, such as password fields. + WriteInteger(kVersion, obj); + WriteString(item.urlString(), obj); + WriteString(item.originalURLString(), obj); + WriteString(item.target(), obj); + WriteString(item.parent(), obj); + WriteString(item.title(), obj); + WriteString(item.alternateTitle(), obj); + WriteReal(item.lastVisitedTime(), obj); + WriteInteger(item.scrollOffset().x, obj); + WriteInteger(item.scrollOffset().y, obj); + WriteBoolean(item.isTargetItem(), obj); + WriteInteger(item.visitCount(), obj); + WriteString(item.referrer(), obj); + + WriteStringVector(item.documentState(), obj); + + if (kVersion >= 9) + WriteInteger64(item.itemSequenceNumber(), obj); + if (kVersion >= 6) + WriteInteger64(item.documentSequenceNumber(), obj); + if (kVersion >= 7) { + bool has_state_object = !item.stateObject().isNull(); + WriteBoolean(has_state_object, obj); + if (has_state_object) + WriteString(item.stateObject().toString(), obj); + } + + // Yes, the referrer is written twice. This is for backwards + // compatibility with the format. + WriteFormData(item.httpBody(), obj); + WriteString(item.httpContentType(), obj); + WriteString(item.referrer(), obj); + + // Subitems + const WebVector& children = item.children(); + WriteInteger(static_cast(children.size()), obj); + for (size_t i = 0, c = children.size(); i < c; ++i) + WriteHistoryItem(children[i], obj); +} + +// Creates a new HistoryItem tree based on the serialized string. +// Assumes the data is in the format returned by WriteHistoryItem. +static WebHistoryItem ReadHistoryItem( + const SerializeObject* obj, bool include_form_data) { + // See note in WriteHistoryItem. on this. + obj->version = ReadInteger(obj); + + if (obj->version == -1) { + GURL url = ReadGURL(obj); + WebHistoryItem item; + item.initialize(); + item.setURLString(WebString::fromUTF8(url.possibly_invalid_spec())); + return item; + } + + if (obj->version > kVersion || obj->version < 1) + return WebHistoryItem(); + + WebHistoryItem item; + item.initialize(); + + item.setURLString(ReadString(obj)); + item.setOriginalURLString(ReadString(obj)); + item.setTarget(ReadString(obj)); + item.setParent(ReadString(obj)); + item.setTitle(ReadString(obj)); + item.setAlternateTitle(ReadString(obj)); + item.setLastVisitedTime(ReadReal(obj)); + int x = ReadInteger(obj); + int y = ReadInteger(obj); + item.setScrollOffset(WebPoint(x, y)); + item.setIsTargetItem(ReadBoolean(obj)); + item.setVisitCount(ReadInteger(obj)); + item.setReferrer(ReadString(obj)); + + item.setDocumentState(ReadStringVector(obj)); + + if (obj->version >= 9) + item.setItemSequenceNumber(ReadInteger64(obj)); + if (obj->version >= 6) + item.setDocumentSequenceNumber(ReadInteger64(obj)); + if (obj->version >= 7) { + bool has_state_object = ReadBoolean(obj); + if (has_state_object) { + item.setStateObject( + WebSerializedScriptValue::fromString(ReadString(obj))); + } + } + + // The extra referrer string is read for backwards compat. + const WebHTTPBody& http_body = ReadFormData(obj); + const WebString& http_content_type = ReadString(obj); + ALLOW_UNUSED const WebString& unused_referrer = ReadString(obj); + if (include_form_data) { + item.setHTTPBody(http_body); + item.setHTTPContentType(http_content_type); + } + + // Subitems + int num_children = ReadInteger(obj); + for (int i = 0; i < num_children; ++i) + item.appendToChildren(ReadHistoryItem(obj, include_form_data)); + + return item; +} + +// Serialize a HistoryItem to a string, using our JSON Value serializer. +std::string HistoryItemToString(const WebHistoryItem& item) { + if (item.isNull()) + return std::string(); + + SerializeObject obj; + WriteHistoryItem(item, &obj); + return obj.GetAsString(); +} + +// Reconstruct a HistoryItem from a string, using our JSON Value deserializer. +// This assumes that the given serialized string has all the required key,value +// pairs, and does minimal error checking. If |include_form_data| is true, +// the form data from a post is restored, otherwise the form data is empty. +static WebHistoryItem HistoryItemFromString( + const std::string& serialized_item, + bool include_form_data) { + if (serialized_item.empty()) + return WebHistoryItem(); + + SerializeObject obj(serialized_item.data(), + static_cast(serialized_item.length())); + return ReadHistoryItem(&obj, include_form_data); +} + +WebHistoryItem HistoryItemFromString( + const std::string& serialized_item) { + return HistoryItemFromString(serialized_item, true); +} + +// For testing purposes only. +void HistoryItemToVersionedString(const WebHistoryItem& item, int version, + std::string* serialized_item) { + if (item.isNull()) { + serialized_item->clear(); + return; + } + + // Temporarily change the version. + int real_version = kVersion; + kVersion = version; + + SerializeObject obj; + WriteHistoryItem(item, &obj); + *serialized_item = obj.GetAsString(); + + kVersion = real_version; +} + +std::string CreateHistoryStateForURL(const GURL& url) { + // We avoid using the WebKit API here, so that we do not need to have WebKit + // initialized before calling this method. Instead, we write a simple + // serialization of the given URL with a dummy version number of -1. This + // will be interpreted by ReadHistoryItem as a request to create a default + // WebHistoryItem. + SerializeObject obj; + WriteInteger(-1, &obj); + WriteGURL(url, &obj); + return obj.GetAsString(); +} + +std::string RemoveFormDataFromHistoryState(const std::string& content_state) { + // TODO(darin): We should avoid using the WebKit API here, so that we do not + // need to have WebKit initialized before calling this method. + const WebHistoryItem& item = HistoryItemFromString(content_state, false); + if (item.isNull()) { + // Couldn't parse the string, return an empty string. + return std::string(); + } + + return HistoryItemToString(item); +} + +} // namespace webkit_glue diff --git a/webkit/glue/glue_serialize.h b/webkit/glue/glue_serialize.h new file mode 100644 index 0000000..303fe3a --- /dev/null +++ b/webkit/glue/glue_serialize.h @@ -0,0 +1,31 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// This file contains (de)serialization (or if you like python, pickling) +// methods for various objects that we want to persist. +// In serialization, we write an object's state to a string in some opaque +// format. Deserialization reconstructs the object's state from such a string. + +#ifndef WEBKIT_GLUE_GLUE_SERIALIZE_H_ +#define WEBKIT_GLUE_GLUE_SERIALIZE_H_ + +#include +#include "third_party/WebKit/WebKit/chromium/public/WebHistoryItem.h" + +namespace webkit_glue { + +// HistoryItem serialization. +std::string HistoryItemToString( + const WebKit::WebHistoryItem& item); +WebKit::WebHistoryItem HistoryItemFromString( + const std::string& serialized_item); + +// For testing purposes only. +void HistoryItemToVersionedString( + const WebKit::WebHistoryItem& item, int version, + std::string* serialized_item); + +} // namespace webkit_glue + +#endif // #ifndef WEBKIT_GLUE_GLUE_SERIALIZE_H_ diff --git a/webkit/glue/glue_serialize_unittest.cc b/webkit/glue/glue_serialize_unittest.cc new file mode 100644 index 0000000..34ce09f --- /dev/null +++ b/webkit/glue/glue_serialize_unittest.cc @@ -0,0 +1,200 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/pickle.h" +#include "base/string_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/WebKit/chromium/public/WebHTTPBody.h" +#include "third_party/WebKit/WebKit/chromium/public/WebPoint.h" +#include "third_party/WebKit/WebKit/chromium/public/WebVector.h" +#include "webkit/glue/glue_serialize.h" + +using WebKit::WebData; +using WebKit::WebFileInfo; +using WebKit::WebHistoryItem; +using WebKit::WebHTTPBody; +using WebKit::WebPoint; +using WebKit::WebString; +using WebKit::WebUChar; +using WebKit::WebVector; + +namespace { +class GlueSerializeTest : public testing::Test { + public: + // Makes a FormData with some random data. + WebHTTPBody MakeFormData() { + WebHTTPBody http_body; + http_body.initialize(); + + const char d1[] = "first data block"; + http_body.appendData(WebData(d1, sizeof(d1)-1)); + + http_body.appendFile(WebString::fromUTF8("file.txt")); + + const char d2[] = "data the second"; + http_body.appendData(WebData(d2, sizeof(d2)-1)); + + return http_body; + } + + // Constructs a HistoryItem with some random data and an optional child. + WebHistoryItem MakeHistoryItem(bool with_form_data, bool pregnant) { + WebHistoryItem item; + item.initialize(); + + item.setURLString(WebString::fromUTF8("urlString")); + item.setOriginalURLString(WebString::fromUTF8("originalURLString")); + item.setTarget(WebString::fromUTF8("target")); + item.setParent(WebString::fromUTF8("parent")); + item.setTitle(WebString::fromUTF8("title")); + item.setAlternateTitle(WebString::fromUTF8("alternateTitle")); + item.setLastVisitedTime(13.37); + item.setScrollOffset(WebPoint(42, -42)); + item.setIsTargetItem(true); + item.setVisitCount(42*42); + + WebVector document_state(size_t(3)); + document_state[0] = WebString::fromUTF8("state1"); + document_state[1] = WebString::fromUTF8("state2"); + document_state[2] = WebString::fromUTF8("state AWESOME"); + item.setDocumentState(document_state); + + // Form Data + if (with_form_data) { + item.setHTTPBody(MakeFormData()); + item.setHTTPContentType(WebString::fromUTF8("formContentType")); + } + + // Setting the FormInfo causes the referrer to be set, so we set the + // referrer after setting the form info. + item.setReferrer(WebString::fromUTF8("referrer")); + + // Children + if (pregnant) + item.appendToChildren(MakeHistoryItem(false, false)); + + return item; + } + + // Checks that a == b. + void HistoryItemExpectEqual(const WebHistoryItem& a, + const WebHistoryItem& b) { + EXPECT_EQ(string16(a.urlString()), string16(b.urlString())); + EXPECT_EQ(string16(a.originalURLString()), string16(b.originalURLString())); + EXPECT_EQ(string16(a.target()), string16(b.target())); + EXPECT_EQ(string16(a.parent()), string16(b.parent())); + EXPECT_EQ(string16(a.title()), string16(b.title())); + EXPECT_EQ(string16(a.alternateTitle()), string16(b.alternateTitle())); + EXPECT_EQ(a.lastVisitedTime(), b.lastVisitedTime()); + EXPECT_EQ(a.scrollOffset(), b.scrollOffset()); + EXPECT_EQ(a.isTargetItem(), b.isTargetItem()); + EXPECT_EQ(a.visitCount(), b.visitCount()); + EXPECT_EQ(string16(a.referrer()), string16(b.referrer())); + + const WebVector& a_docstate = a.documentState(); + const WebVector& b_docstate = b.documentState(); + EXPECT_EQ(a_docstate.size(), b_docstate.size()); + for (size_t i = 0, c = a_docstate.size(); i < c; ++i) + EXPECT_EQ(string16(a_docstate[i]), string16(b_docstate[i])); + + // Form Data + const WebHTTPBody& a_body = a.httpBody(); + const WebHTTPBody& b_body = b.httpBody(); + EXPECT_EQ(!a_body.isNull(), !b_body.isNull()); + if (!a_body.isNull() && !b_body.isNull()) { + EXPECT_EQ(a_body.elementCount(), b_body.elementCount()); + WebHTTPBody::Element a_elem, b_elem; + for (size_t i = 0; a_body.elementAt(i, a_elem) && + b_body.elementAt(i, b_elem); ++i) { + EXPECT_EQ(a_elem.type, b_elem.type); + if (a_elem.type == WebHTTPBody::Element::TypeData) { + EXPECT_EQ(std::string(a_elem.data.data(), a_elem.data.size()), + std::string(b_elem.data.data(), b_elem.data.size())); + } else { + EXPECT_EQ(string16(a_elem.filePath), string16(b_elem.filePath)); + } + } + } + EXPECT_EQ(string16(a.httpContentType()), string16(b.httpContentType())); + + // Children + const WebVector& a_children = a.children(); + const WebVector& b_children = b.children(); + EXPECT_EQ(a_children.size(), b_children.size()); + for (size_t i = 0, c = a_children.size(); i < c; ++i) + HistoryItemExpectEqual(a_children[i], b_children[i]); + } +}; + +// Test old versions of serialized data to ensure that newer versions of code +// can still read history items written by previous versions. +TEST_F(GlueSerializeTest, BackwardsCompatibleTest) { + const WebHistoryItem& item = MakeHistoryItem(false, false); + + // Make sure version 3 (current version) can read versions 1 and 2. + for (int i = 1; i <= 2; i++) { + std::string serialized_item; + webkit_glue::HistoryItemToVersionedString(item, i, &serialized_item); + const WebHistoryItem& deserialized_item = + webkit_glue::HistoryItemFromString(serialized_item); + ASSERT_FALSE(item.isNull()); + ASSERT_FALSE(deserialized_item.isNull()); + HistoryItemExpectEqual(item, deserialized_item); + } +} + +// Makes sure that a HistoryItem remains intact after being serialized and +// deserialized. +TEST_F(GlueSerializeTest, HistoryItemSerializeTest) { + const WebHistoryItem& item = MakeHistoryItem(true, true); + const std::string& serialized_item = webkit_glue::HistoryItemToString(item); + const WebHistoryItem& deserialized_item = + webkit_glue::HistoryItemFromString(serialized_item); + + ASSERT_FALSE(item.isNull()); + ASSERT_FALSE(deserialized_item.isNull()); + HistoryItemExpectEqual(item, deserialized_item); +} + +// Checks that broken messages don't take out our process. +TEST_F(GlueSerializeTest, BadMessagesTest) { + { + Pickle p; + // Version 1 + p.WriteInt(1); + // Empty strings. + for (int i = 0; i < 6; ++i) + p.WriteInt(-1); + // Bad real number. + p.WriteInt(-1); + std::string s(static_cast(p.data()), p.size()); + webkit_glue::HistoryItemFromString(s); + } + { + double d = 0; + Pickle p; + // Version 1 + p.WriteInt(1); + // Empty strings. + for (int i = 0; i < 6; ++i) + p.WriteInt(-1); + // More misc fields. + p.WriteData(reinterpret_cast(&d), sizeof(d)); + p.WriteInt(1); + p.WriteInt(1); + p.WriteInt(0); + p.WriteInt(0); + p.WriteInt(-1); + p.WriteInt(0); + // WebForm + p.WriteInt(1); + p.WriteInt(WebHTTPBody::Element::TypeData); + std::string s(static_cast(p.data()), p.size()); + webkit_glue::HistoryItemFromString(s); + } +} + +} // namespace diff --git a/webkit/glue/iframe_redirect_unittest.cc b/webkit/glue/iframe_redirect_unittest.cc new file mode 100644 index 0000000..9170d19 --- /dev/null +++ b/webkit/glue/iframe_redirect_unittest.cc @@ -0,0 +1,48 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/file_util.h" +#include "base/string_util.h" +#include "third_party/WebKit/WebKit/chromium/public/WebDataSource.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURL.h" +#include "third_party/WebKit/WebKit/chromium/public/WebVector.h" +#include "third_party/WebKit/WebKit/chromium/public/WebView.h" +#include "webkit/glue/webkit_glue.h" +#include "webkit/tools/test_shell/test_shell_test.h" + +using WebKit::WebDataSource; +using WebKit::WebFrame; +using WebKit::WebString; +using WebKit::WebURL; +using WebKit::WebVector; + +typedef TestShellTest IFrameRedirectTest; + +// Tests that loading a page in an iframe from javascript results in +// a redirect from about:blank. +TEST_F(IFrameRedirectTest, Test) { + FilePath iframes_data_dir_ = data_dir_; + iframes_data_dir_ = iframes_data_dir_.AppendASCII("test_shell"); + iframes_data_dir_ = iframes_data_dir_.AppendASCII("iframe_redirect"); + ASSERT_TRUE(file_util::PathExists(iframes_data_dir_)); + + GURL test_url = GetTestURL(iframes_data_dir_, "main.html"); + + test_shell_->LoadURL(test_url); + test_shell_->WaitTestFinished(); + + WebFrame* iframe = + test_shell_->webView()->findFrameByName(WebString::fromUTF8("ifr")); + ASSERT_TRUE(iframe != NULL); + WebDataSource* iframe_ds = iframe->dataSource(); + ASSERT_TRUE(iframe_ds != NULL); + WebVector redirects; + iframe_ds->redirectChain(redirects); + ASSERT_FALSE(redirects.isEmpty()); + ASSERT_TRUE(GURL(redirects[0]) == GURL("about:blank")); +} diff --git a/webkit/glue/image_decoder.cc b/webkit/glue/image_decoder.cc new file mode 100644 index 0000000..6850909 --- /dev/null +++ b/webkit/glue/image_decoder.cc @@ -0,0 +1,41 @@ +// Copyright (c) 2006-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 "webkit/glue/image_decoder.h" + +#include "third_party/WebKit/WebKit/chromium/public/WebData.h" +#include "third_party/WebKit/WebKit/chromium/public/WebImage.h" +#include "third_party/WebKit/WebKit/chromium/public/WebSize.h" +#include "third_party/skia/include/core/SkBitmap.h" + +#if WEBKIT_USING_CG +#include "skia/ext/skia_utils_mac.h" +#endif + +using WebKit::WebData; +using WebKit::WebImage; + +namespace webkit_glue { + +ImageDecoder::ImageDecoder() : desired_icon_size_(0, 0) { +} + +ImageDecoder::ImageDecoder(const gfx::Size& desired_icon_size) + : desired_icon_size_(desired_icon_size) { +} + +ImageDecoder::~ImageDecoder() { +} + +SkBitmap ImageDecoder::Decode(const unsigned char* data, size_t size) const { + const WebImage& image = WebImage::fromData( + WebData(reinterpret_cast(data), size), desired_icon_size_); +#if WEBKIT_USING_SKIA + return image.getSkBitmap(); +#elif WEBKIT_USING_CG + return gfx::CGImageToSkBitmap(image.getCGImageRef()); +#endif +} + +} // namespace webkit_glue diff --git a/webkit/glue/image_decoder.h b/webkit/glue/image_decoder.h new file mode 100644 index 0000000..62d9253 --- /dev/null +++ b/webkit/glue/image_decoder.h @@ -0,0 +1,37 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/basictypes.h" +#include "gfx/size.h" + +class SkBitmap; + +namespace webkit_glue { + +// Provides an interface to WebKit's image decoders. +// +// Note to future: This class should be deleted. We should have our own nice +// image decoders in base/gfx, and our port should use those. Currently, it's +// the other way around. +class ImageDecoder { + public: + // Use the constructor with desired_size when you think you may have an .ico + // format and care about which size you get back. Otherwise, use the 0-arg + // constructor. + ImageDecoder(); + ImageDecoder(const gfx::Size& desired_icon_size); + ~ImageDecoder(); + + // Call this function to decode the image. If successful, the decoded image + // will be returned. Otherwise, an empty bitmap will be returned. + SkBitmap Decode(const unsigned char* data, size_t size) const; + + private: + // Size will be empty to get the largest possible size. + gfx::Size desired_icon_size_; + + DISALLOW_COPY_AND_ASSIGN(ImageDecoder); +}; + +} // namespace webkit_glue diff --git a/webkit/glue/image_resource_fetcher.cc b/webkit/glue/image_resource_fetcher.cc new file mode 100644 index 0000000..6185834 --- /dev/null +++ b/webkit/glue/image_resource_fetcher.cc @@ -0,0 +1,54 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/glue/image_resource_fetcher.h" + +#include "base/callback.h" +#include "gfx/size.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "webkit/glue/image_decoder.h" +#include "third_party/skia/include/core/SkBitmap.h" + +using WebKit::WebFrame; + +namespace webkit_glue { + +ImageResourceFetcher::ImageResourceFetcher( + const GURL& image_url, + WebFrame* frame, + int id, + int image_size, + Callback* callback) + : callback_(callback), + id_(id), + image_url_(image_url), + image_size_(image_size) { + fetcher_.reset(new ResourceFetcher( + image_url, frame, + NewCallback(this, &ImageResourceFetcher::OnURLFetchComplete))); +} + +ImageResourceFetcher::~ImageResourceFetcher() { + if (!fetcher_->completed()) + fetcher_->Cancel(); +} + +void ImageResourceFetcher::OnURLFetchComplete( + const WebKit::WebURLResponse& response, + const std::string& data) { + SkBitmap bitmap; + if (!response.isNull() && response.httpStatusCode() == 200) { + // Request succeeded, try to convert it to an image. + ImageDecoder decoder(gfx::Size(image_size_, image_size_)); + bitmap = decoder.Decode( + reinterpret_cast(data.data()), data.size()); + } // else case: + // If we get here, it means no image from server or couldn't decode the + // response as an image. The delegate will see a null image, indicating + // that an error occurred. + callback_->Run(this, bitmap); + callback_.reset(); +} + +} // namespace webkit_glue diff --git a/webkit/glue/image_resource_fetcher.h b/webkit/glue/image_resource_fetcher.h new file mode 100644 index 0000000..8c6f70c --- /dev/null +++ b/webkit/glue/image_resource_fetcher.h @@ -0,0 +1,63 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_IMAGE_RESOURCE_FETCHER_H_ +#define WEBKIT_GLUE_IMAGE_RESOURCE_FETCHER_H_ + +#include "base/basictypes.h" +#include "base/callback.h" +#include "webkit/glue/resource_fetcher.h" + +class SkBitmap; + +namespace webkit_glue { + +// ImageResourceFetcher handles downloading an image for a webview. Once +// downloading is done the supplied callback is notified. ImageResourceFetcher +// is used to download the favicon and images for web apps. +class ImageResourceFetcher { + public: + typedef Callback2::Type Callback; + + ImageResourceFetcher(const GURL& image_url, + WebKit::WebFrame* frame, + int id, + int image_size, + Callback* callback); + + virtual ~ImageResourceFetcher(); + + // URL of the image we're downloading. + const GURL& image_url() const { return image_url_; } + + // Unique identifier for the request. + int id() const { return id_; } + + private: + // ResourceFetcher::Callback. Decodes the image and invokes callback_. + void OnURLFetchComplete(const WebKit::WebURLResponse& response, + const std::string& data); + + scoped_ptr callback_; + + // Unique identifier for the request. + const int id_; + + // URL of the image. + const GURL image_url_; + + // The size of the image. This is only a hint that is used if the image + // contains multiple sizes. A value of 0 results in using the first frame + // of the image. + const int image_size_; + + // Does the actual download. + scoped_ptr fetcher_; + + DISALLOW_COPY_AND_ASSIGN(ImageResourceFetcher); +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_IMAGE_RESOURCE_FETCHER_H_ diff --git a/webkit/glue/inspector_strings.grd b/webkit/glue/inspector_strings.grd new file mode 100644 index 0000000..9d634b7 --- /dev/null +++ b/webkit/glue/inspector_strings.grd @@ -0,0 +1,640 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ''' (%d1) + + + ''' (repeated %d2 times) + + + %.0f100B + + + %.0f100ms + + + %.1f5 days + + + %.1f5hrs + + + %.1f5min + + + %.2f5%% + + + %.2f5KB + + + %.2f5s + + + %.3f5MB + + + %.3f5ms + + + %d1 error + + + %1$d1 error, %2$d1 warning + + + %1$d1 error, %2$d2 warnings + + + %d2 errors + + + %1$d2 errors, %2$d1 warning + + + %1$d2 errors, %2$d1 warnings + + + %d2 matches + + + %d1 style change + + + %d2 style changes + + + %d1 warning + + + %d2 warnings + + + %1$d100 × %2$d100 + + + %spicture.gif (from cache) + + + %s5ms download + + + %s5ms latency + + + %1$s5ms latency, %2$s5ms download (%3$s10ms total) + + + (anonymous function) + + + (program) + + + (program): %shttp://site/script.js + + + (text) + + + (whitespace) + + + 1 match + + + Always enable + + + An error occurred trying to\nread the “%sCLIENTS” table. + + + An unexpected error %sNo memory occurred. + + + Average + + + Breakpoints + + + Call Stack + + + Calls + + + Clear changes log. + + + Clear console log. + + + Closure + + + COOKIES + + + Computed Style + + + Cookies + + + DATABASES + + + DOM + + + Database no longer has expected version. + + + Databases + + + Debugging disabled. Click to enable. + + + Debugging enabled. Click to disable. + + + Debugging scripts requires you to start the debugger. + + + Delete + + + Dimensions + + + Dock to main window. + + + Documents + + + Don't pause on exceptions. + + + Double-Click to Add + + + Elements + + + Enable Debugging + + + Enable Profiling + + + Enable resource tracking + + + Enabling debugging will make scripts run slower. + + + Enabling profiling will make scripts run slower. + + + Enabling resource tracking will reload the page and make page loading slower. + + + Event Document + + + Event Target + + + Exclude selected function. + + + File size + + + Focus selected function. + + + Fonts + + + Function + + + GRAPHS + + + Global + + + Heavy (Bottom Up) + + + Hide changes view. + + + Hide console. + + + Images + + + Inline Style Attribute + + + Key + + + LOCAL STORAGE + + + Local + + + MIME type + + + Metrics + + + No Breakpoints + + + No Properties + + + No Variables + + + Not Found + + + Not Paused + + + Only enable for this session + + + Other + + + Pause on exceptions. + + + Pause script execution. + + + Paused + + + Pausing + + + Profile %d1 + + + Profiles + + + Profiling disabled. Click to enable. + + + Profiling enabled. Click to disable. + + + Properties + + + Prototype + + + Query + + + RESOURCES + + + Refresh + + + Request Headers + + + Resource interpreted as %1$stext/javascript but transferred with MIME type %2$stext/javascript. + + + Resource tracking disabled. Click to enable. + + + Resource tracking enabled. Click to disable. + + + Resources + + + Response Headers + + + Restore all functions. + + + Run %d1 + + + SESSION STORAGE + + + Scope Variables + + + Scripts + + + Search %sfoo + + + Select an element in the page to inspect it. + + + Self + + + Show absolute total and self times. + + + Show changes view. + + + Show console. + + + Show inherited + + + Show the next script resource. + + + Show the previous script resource. + + + Show total and self times as percentages. + + + Size + + + Sort by Duration + + + Sort by End Time + + + Sort by Latency + + + Sort by Response Time + + + Sort by Size + + + Sort by Start Time + + + Source + + + Start profiling. + + + Step into next function call. + + + Step out of current function. + + + Step over next function call. + + + Stepping + + + Stop profiling. + + + Style Attribute + + + Styles + + + Stylesheets + + + The “%sfoo”\ntable is empty. + + + This site has no cookies. + + + This storage is empty. + + + Time + + + Total + + + Tree (Top Down) + + + Undock into separate window. + + + Use large resource rows. + + + Use small resource rows. + + + Value + + + With Block + + + XHR + + + You could save bandwidth by having your web server compress this transfer with gzip or zlib. + + + You need to enable debugging before you can use the Scripts panel. + + + You need to enable profiling before you can use the Profiles panel. + + + You need to enable resource tracking to use this panel. + + + border + + + content + + + document + + + element’s “%swidth” attribute + + + font + + + image + + + inline stylesheet + + + line %d25 + + + margin + + + other + + + padding + + + position + + + script + + + stylesheet + + + user agent stylesheet + + + user stylesheet + + + via inspector + + + + diff --git a/webkit/glue/media/buffered_data_source.cc b/webkit/glue/media/buffered_data_source.cc new file mode 100644 index 0000000..dfac588 --- /dev/null +++ b/webkit/glue/media/buffered_data_source.cc @@ -0,0 +1,1052 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/message_loop.h" +#include "base/process_util.h" +#include "base/stl_util-inl.h" +#include "base/string_util.h" +#include "media/base/filter_host.h" +#include "media/base/media_format.h" +#include "net/base/load_flags.h" +#include "net/base/net_errors.h" +#include "net/http/http_response_headers.h" +#include "webkit/glue/media/buffered_data_source.h" +#include "webkit/glue/webkit_glue.h" + +namespace { + +const char kHttpScheme[] = "http"; +const char kHttpsScheme[] = "https"; +const char kDataScheme[] = "data"; +const int64 kPositionNotSpecified = -1; +const int kHttpOK = 200; +const int kHttpPartialContent = 206; + +// Define the number of bytes in a megabyte. +const size_t kMegabyte = 1024 * 1024; + +// Backward capacity of the buffer, by default 2MB. +const size_t kBackwardCapcity = 2 * kMegabyte; + +// Forward capacity of the buffer, by default 10MB. +const size_t kForwardCapacity = 10 * kMegabyte; + +// The threshold of bytes that we should wait until the data arrives in the +// future instead of restarting a new connection. This number is defined in the +// number of bytes, we should determine this value from typical connection speed +// and amount of time for a suitable wait. Now I just make a guess for this +// number to be 2MB. +// TODO(hclam): determine a better value for this. +const int kForwardWaitThreshold = 2 * kMegabyte; + +// Defines how long we should wait for more data before we declare a connection +// timeout and start a new request. +// TODO(hclam): Set it to 5s, calibrate this value later. +const int kTimeoutMilliseconds = 5000; + +// Defines how many times we should try to read from a buffered resource loader +// before we declare a read error. After each failure of read from a buffered +// resource loader, a new one is created to be read. +const int kReadTrials = 3; + +// BufferedDataSource has an intermediate buffer, this value governs the initial +// size of that buffer. It is set to 32KB because this is a typical read size +// of FFmpeg. +const int kInitialReadBufferSize = 32768; + +// Returns true if |url| operates on HTTP protocol. +bool IsHttpProtocol(const GURL& url) { + return url.SchemeIs(kHttpScheme) || url.SchemeIs(kHttpsScheme); +} + +bool IsDataProtocol(const GURL& url) { + return url.SchemeIs(kDataScheme); +} + +} // namespace + +namespace webkit_glue { +///////////////////////////////////////////////////////////////////////////// +// BufferedResourceLoader +BufferedResourceLoader::BufferedResourceLoader( + webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory, + const GURL& url, + int64 first_byte_position, + int64 last_byte_position) + : buffer_(new media::SeekableBuffer(kBackwardCapcity, kForwardCapacity)), + deferred_(false), + defer_allowed_(true), + completed_(false), + range_requested_(false), + partial_response_(false), + bridge_factory_(bridge_factory), + url_(url), + first_byte_position_(first_byte_position), + last_byte_position_(last_byte_position), + start_callback_(NULL), + bridge_(NULL), + offset_(0), + content_length_(kPositionNotSpecified), + instance_size_(kPositionNotSpecified), + read_callback_(NULL), + read_position_(0), + read_size_(0), + read_buffer_(NULL), + first_offset_(0), + last_offset_(0) { +} + +BufferedResourceLoader::~BufferedResourceLoader() { +} + +void BufferedResourceLoader::Start(net::CompletionCallback* start_callback, + NetworkEventCallback* event_callback) { + // Make sure we have not started. + DCHECK(!bridge_.get()); + DCHECK(!start_callback_.get()); + DCHECK(!event_callback_.get()); + DCHECK(start_callback); + DCHECK(event_callback); + + start_callback_.reset(start_callback); + event_callback_.reset(event_callback); + + if (first_byte_position_ != kPositionNotSpecified) { + range_requested_ = true; + // TODO(hclam): server may not support range request so |offset_| may not + // equal to |first_byte_position_|. + offset_ = first_byte_position_; + } + + // Creates the bridge on render thread since we can only access + // ResourceDispatcher on this thread. + bridge_.reset( + bridge_factory_->CreateBridge( + url_, + IsMediaCacheEnabled() ? net::LOAD_NORMAL : net::LOAD_BYPASS_CACHE, + first_byte_position_, + last_byte_position_)); + + // Increment the reference count right before we start the request. This + // reference will be release when this request has ended. + AddRef(); + + // And start the resource loading. + bridge_->Start(this); +} + +void BufferedResourceLoader::Stop() { + // Reset callbacks. + start_callback_.reset(); + event_callback_.reset(); + read_callback_.reset(); + + // Use the internal buffer to signal that we have been stopped. + // TODO(hclam): Not so pretty to do this. + if (!buffer_.get()) + return; + + // Destroy internal buffer. + buffer_.reset(); + + if (bridge_.get()) { + // Cancel the request. This method call will cancel the request + // asynchronously. We may still get data or messages until we receive + // a response completed message. + if (deferred_) + bridge_->SetDefersLoading(false); + deferred_ = false; + bridge_->Cancel(); + } +} + +void BufferedResourceLoader::Read(int64 position, + int read_size, + uint8* buffer, + net::CompletionCallback* read_callback) { + DCHECK(!read_callback_.get()); + DCHECK(buffer_.get()); + DCHECK(read_callback); + DCHECK(buffer); + + // Save the parameter of reading. + read_callback_.reset(read_callback); + read_position_ = position; + read_size_ = read_size; + read_buffer_ = buffer; + + // If read position is beyond the instance size, we cannot read there. + if (instance_size_ != kPositionNotSpecified && + instance_size_ <= read_position_) { + DoneRead(0); + return; + } + + // Make sure |offset_| and |read_position_| does not differ by a large + // amount. + if (read_position_ > offset_ + kint32max || + read_position_ < offset_ + kint32min) { + DoneRead(net::ERR_CACHE_MISS); + return; + } + + // Prepare the parameters. + first_offset_ = static_cast(read_position_ - offset_); + last_offset_ = first_offset_ + read_size_; + + // If we can serve the request now, do the actual read. + if (CanFulfillRead()) { + ReadInternal(); + DisableDeferIfNeeded(); + return; + } + + // If we expected the read request to be fulfilled later, returns + // immediately and let more data to flow in. + if (WillFulfillRead()) + return; + + // Make a callback to report failure. + DoneRead(net::ERR_CACHE_MISS); +} + +int64 BufferedResourceLoader::GetBufferedFirstBytePosition() { + if (buffer_.get()) + return offset_ - static_cast(buffer_->backward_bytes()); + return kPositionNotSpecified; +} + +int64 BufferedResourceLoader::GetBufferedLastBytePosition() { + if (buffer_.get()) + return offset_ + static_cast(buffer_->forward_bytes()) - 1; + return kPositionNotSpecified; +} + +void BufferedResourceLoader::SetAllowDefer(bool is_allowed) { + defer_allowed_ = is_allowed; + DisableDeferIfNeeded(); +} + +///////////////////////////////////////////////////////////////////////////// +// BufferedResourceLoader, +// webkit_glue::ResourceLoaderBridge::Peer implementations +bool BufferedResourceLoader::OnReceivedRedirect( + const GURL& new_url, + const webkit_glue::ResourceLoaderBridge::ResponseInfo& info, + bool* has_new_first_party_for_cookies, + GURL* new_first_party_for_cookies) { + DCHECK(bridge_.get()); + + // Save the new URL. + url_ = new_url; + // TODO(wtc): should we return a new first party for cookies URL? + *has_new_first_party_for_cookies = false; + + // The load may have been stopped and |start_callback| is destroyed. + // In this case we shouldn't do anything. + if (!start_callback_.get()) + return true; + + if (!IsProtocolSupportedForMedia(new_url)) { + DoneStart(net::ERR_ADDRESS_INVALID); + Stop(); + return false; + } + return true; +} + +void BufferedResourceLoader::OnReceivedResponse( + const webkit_glue::ResourceLoaderBridge::ResponseInfo& info, + bool content_filtered) { + DCHECK(bridge_.get()); + + // The loader may have been stopped and |start_callback| is destroyed. + // In this case we shouldn't do anything. + if (!start_callback_.get()) + return; + + // We make a strong assumption that when we reach here we have either + // received a response from HTTP/HTTPS protocol or the request was + // successful (in particular range request). So we only verify the partial + // response for HTTP and HTTPS protocol. + if (IsHttpProtocol(url_)) { + int error = net::OK; + if (!info.headers) { + // We expect to receive headers because this is a HTTP or HTTPS protocol, + // if not report failure. + error = net::ERR_INVALID_RESPONSE; + } else { + if (info.headers->response_code() == kHttpPartialContent) + partial_response_ = true; + + if (range_requested_ && partial_response_) { + // If we have verified the partial response and it is correct, we will + // return net::OK. + if (!VerifyPartialResponse(info)) + error = net::ERR_INVALID_RESPONSE; + } else if (info.headers->response_code() != kHttpOK) { + // We didn't request a range but server didn't reply with "200 OK". + error = net::ERR_FAILED; + } + } + + if (error != net::OK) { + DoneStart(error); + Stop(); + return; + } + } else { + // For any protocol other than HTTP and HTTPS, assume range request is + // always fulfilled. + partial_response_ = range_requested_; + } + + // |info.content_length| can be -1, in that case |content_length_| is + // not specified and this is a streaming response. + content_length_ = info.content_length; + + // If we have not requested a range, then the size of the instance is equal + // to the content length. + if (!partial_response_) + instance_size_ = content_length_; + + // Calls with a successful response. + DoneStart(net::OK); +} + +void BufferedResourceLoader::OnReceivedData(const char* data, int len) { + DCHECK(bridge_.get()); + + // If this loader has been stopped, |buffer_| would be destroyed. + // In this case we shouldn't do anything. + if (!buffer_.get()) + return; + + // Writes more data to |buffer_|. + buffer_->Append(reinterpret_cast(data), len); + + // If there is an active read request, try to fulfill the request. + if (HasPendingRead() && CanFulfillRead()) { + ReadInternal(); + } else if (!defer_allowed_) { + // If we're not allowed to defer, slide the buffer window forward instead + // of deferring. + if (buffer_->forward_bytes() > buffer_->forward_capacity()) { + size_t excess = buffer_->forward_bytes() - buffer_->forward_capacity(); + bool success = buffer_->Seek(excess); + DCHECK(success); + offset_ += first_offset_ + excess; + } + } + + // At last see if the buffer is full and we need to defer the downloading. + EnableDeferIfNeeded(); + + // Notify that we have received some data. + NotifyNetworkEvent(); +} + +void BufferedResourceLoader::OnCompletedRequest( + const URLRequestStatus& status, const std::string& security_info) { + DCHECK(bridge_.get()); + + // Saves the information that the request has completed. + completed_ = true; + + // If there is a start callback, calls it. + if (start_callback_.get()) { + DoneStart(status.os_error()); + } + + // If there is a pending read but the request has ended, returns with what + // we have. + if (HasPendingRead()) { + // Make sure we have a valid buffer before we satisfy a read request. + DCHECK(buffer_.get()); + + if (status.is_success()) { + // Try to fulfill with what is in the buffer. + if (CanFulfillRead()) + ReadInternal(); + else + DoneRead(net::ERR_CACHE_MISS); + } else { + // If the request has failed, then fail the read. + DoneRead(net::ERR_FAILED); + } + } + + // There must not be any outstanding read request. + DCHECK(!HasPendingRead()); + + // Notify that network response is completed. + NotifyNetworkEvent(); + + // We incremented the reference count when the loader was started. We balance + // that reference here so that we get destroyed. This is also the only safe + // place to destroy the ResourceLoaderBridge. + bridge_.reset(); + Release(); +} + +///////////////////////////////////////////////////////////////////////////// +// BufferedResourceLoader, private +void BufferedResourceLoader::EnableDeferIfNeeded() { + if (!defer_allowed_) + return; + + if (!deferred_ && + buffer_->forward_bytes() >= buffer_->forward_capacity()) { + deferred_ = true; + + if (bridge_.get()) + bridge_->SetDefersLoading(true); + + NotifyNetworkEvent(); + } +} + +void BufferedResourceLoader::DisableDeferIfNeeded() { + if (deferred_ && + (!defer_allowed_ || + buffer_->forward_bytes() < buffer_->forward_capacity() / 2)) { + deferred_ = false; + + if (bridge_.get()) + bridge_->SetDefersLoading(false); + + NotifyNetworkEvent(); + } +} + +bool BufferedResourceLoader::CanFulfillRead() { + // If we are reading too far in the backward direction. + if (first_offset_ < 0 && + first_offset_ + static_cast(buffer_->backward_bytes()) < 0) + return false; + + // If the start offset is too far ahead. + if (first_offset_ >= static_cast(buffer_->forward_bytes())) + return false; + + // At the point, we verified that first byte requested is within the buffer. + // If the request has completed, then just returns with what we have now. + if (completed_) + return true; + + // If the resource request is still active, make sure the whole requested + // range is covered. + if (last_offset_ > static_cast(buffer_->forward_bytes())) + return false; + + return true; +} + +bool BufferedResourceLoader::WillFulfillRead() { + // Reading too far in the backward direction. + if (first_offset_ < 0 && + first_offset_ + static_cast(buffer_->backward_bytes()) < 0) + return false; + + // Try to read too far ahead. + if (last_offset_ > kForwardWaitThreshold) + return false; + + // The resource request has completed, there's no way we can fulfill the + // read request. + if (completed_) + return false; + + return true; +} + +void BufferedResourceLoader::ReadInternal() { + // Seek to the first byte requested. + bool ret = buffer_->Seek(first_offset_); + DCHECK(ret); + + // Then do the read. + int read = static_cast(buffer_->Read(read_buffer_, read_size_)); + offset_ += first_offset_ + read; + + // And report with what we have read. + DoneRead(read); +} + +bool BufferedResourceLoader::VerifyPartialResponse( + const ResourceLoaderBridge::ResponseInfo& info) { + int64 first_byte_position, last_byte_position, instance_size; + if (!info.headers->GetContentRange(&first_byte_position, + &last_byte_position, + &instance_size)) { + return false; + } + + if (instance_size != kPositionNotSpecified) + instance_size_ = instance_size; + + if (first_byte_position_ != -1 && + first_byte_position_ != first_byte_position) { + return false; + } + + // TODO(hclam): I should also check |last_byte_position|, but since + // we will never make such a request that it is ok to leave it unimplemented. + return true; +} + +void BufferedResourceLoader::DoneRead(int error) { + read_callback_->RunWithParams(Tuple1(error)); + read_callback_.reset(); + read_position_ = 0; + read_size_ = 0; + read_buffer_ = NULL; + first_offset_ = 0; + last_offset_ = 0; +} + +void BufferedResourceLoader::DoneStart(int error) { + start_callback_->RunWithParams(Tuple1(error)); + start_callback_.reset(); +} + +void BufferedResourceLoader::NotifyNetworkEvent() { + if (event_callback_.get()) + event_callback_->Run(); +} + +///////////////////////////////////////////////////////////////////////////// +// BufferedDataSource, static methods +bool BufferedDataSource::IsMediaFormatSupported( + const media::MediaFormat& media_format) { + std::string mime_type; + std::string url; + if (media_format.GetAsString(media::MediaFormat::kMimeType, &mime_type) && + media_format.GetAsString(media::MediaFormat::kURL, &url)) { + GURL gurl(url); + + // This data source doesn't support data:// protocol, so reject it + // explicitly. + if (IsProtocolSupportedForMedia(gurl) && !IsDataProtocol(gurl)) + return true; + } + return false; +} + +///////////////////////////////////////////////////////////////////////////// +// BufferedDataSource, protected +BufferedDataSource::BufferedDataSource( + MessageLoop* render_loop, + webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory) + : total_bytes_(kPositionNotSpecified), + loaded_(false), + streaming_(false), + bridge_factory_(bridge_factory), + loader_(NULL), + network_activity_(false), + initialize_callback_(NULL), + read_callback_(NULL), + read_position_(0), + read_size_(0), + read_buffer_(NULL), + read_attempts_(0), + intermediate_read_buffer_(new uint8[kInitialReadBufferSize]), + intermediate_read_buffer_size_(kInitialReadBufferSize), + render_loop_(render_loop), + stop_signal_received_(false), + stopped_on_render_loop_(false), + media_is_paused_(true) { +} + +BufferedDataSource::~BufferedDataSource() { +} + +// A factory method to create BufferedResourceLoader using the read parameters. +// This method can be overrided to inject mock BufferedResourceLoader object +// for testing purpose. +BufferedResourceLoader* BufferedDataSource::CreateResourceLoader( + int64 first_byte_position, int64 last_byte_position) { + DCHECK(MessageLoop::current() == render_loop_); + + return new BufferedResourceLoader(bridge_factory_.get(), url_, + first_byte_position, + last_byte_position); +} + +// This method simply returns kTimeoutMilliseconds. The purpose of this +// method is to be overidded so as to provide a different timeout value +// for testing purpose. +base::TimeDelta BufferedDataSource::GetTimeoutMilliseconds() { + return base::TimeDelta::FromMilliseconds(kTimeoutMilliseconds); +} + +///////////////////////////////////////////////////////////////////////////// +// BufferedDataSource, media::MediaFilter implementation +void BufferedDataSource::Initialize(const std::string& url, + media::FilterCallback* callback) { + // Saves the url. + url_ = GURL(url); + + if (!IsProtocolSupportedForMedia(url_)) { + // This method is called on the thread where host() lives so it is safe + // to make this call. + host()->SetError(media::PIPELINE_ERROR_NETWORK); + callback->Run(); + delete callback; + return; + } + + DCHECK(callback); + initialize_callback_.reset(callback); + + media_format_.SetAsString(media::MediaFormat::kMimeType, + media::mime_type::kApplicationOctetStream); + media_format_.SetAsString(media::MediaFormat::kURL, url); + + // Post a task to complete the initialization task. + render_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, &BufferedDataSource::InitializeTask)); +} + +void BufferedDataSource::Stop(media::FilterCallback* callback) { + { + AutoLock auto_lock(lock_); + stop_signal_received_ = true; + } + if (callback) { + callback->Run(); + delete callback; + } + render_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, &BufferedDataSource::CleanupTask)); +} + +void BufferedDataSource::SetPlaybackRate(float playback_rate) { + render_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, &BufferedDataSource::SetPlaybackRateTask, + playback_rate)); +} + +///////////////////////////////////////////////////////////////////////////// +// BufferedDataSource, media::DataSource implementation +void BufferedDataSource::Read(int64 position, size_t size, uint8* data, + media::DataSource::ReadCallback* read_callback) { + render_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, &BufferedDataSource::ReadTask, + position, static_cast(size), data, read_callback)); +} + +bool BufferedDataSource::GetSize(int64* size_out) { + if (total_bytes_ != kPositionNotSpecified) { + *size_out = total_bytes_; + return true; + } + *size_out = 0; + return false; +} + +bool BufferedDataSource::IsStreaming() { + return streaming_; +} + +///////////////////////////////////////////////////////////////////////////// +// BufferedDataSource, render thread tasks +void BufferedDataSource::InitializeTask() { + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(!loader_.get()); + DCHECK(!stopped_on_render_loop_); + + // Kick starts the watch dog task that will handle connection timeout. + // We run the watch dog 2 times faster the actual timeout so as to catch + // the timeout more accurately. + watch_dog_timer_.Start( + GetTimeoutMilliseconds() / 2, + this, + &BufferedDataSource::WatchDogTask); + + if (IsHttpProtocol(url_)) { + // Fetch only first 1024 bytes as this usually covers the header portion + // of a media file that gives enough information about the codecs, etc. + // This also serve as a probe to determine server capability to serve + // range request. + // TODO(hclam): Do some experiments for the best approach. + loader_ = CreateResourceLoader(0, 1024); + loader_->Start( + NewCallback(this, &BufferedDataSource::HttpInitialStartCallback), + NewCallback(this, &BufferedDataSource::NetworkEventCallback)); + } else { + // For all other protocols, assume they support range request. We fetch + // the full range of the resource to obtain the instance size because + // we won't be served HTTP headers. + loader_ = CreateResourceLoader(-1, -1); + loader_->Start( + NewCallback(this, &BufferedDataSource::NonHttpInitialStartCallback), + NewCallback(this, &BufferedDataSource::NetworkEventCallback)); + } +} + +void BufferedDataSource::ReadTask( + int64 position, int read_size, uint8* buffer, + media::DataSource::ReadCallback* read_callback) { + DCHECK(MessageLoop::current() == render_loop_); + + // If CleanupTask() was executed we should return immediately. We check this + // variable to prevent doing any actual work after clean up was done. We do + // not check |stop_signal_received_| because anything use of it has to be + // within |lock_| which is not desirable. + if (stopped_on_render_loop_) + return; + + DCHECK(!read_callback_.get()); + DCHECK(read_callback); + + // Saves the read parameters. + read_position_ = position; + read_size_ = read_size; + read_callback_.reset(read_callback); + read_buffer_ = buffer; + read_submitted_time_ = base::Time::Now(); + read_attempts_ = 0; + + // Call to read internal to perform the actual read. + ReadInternal(); +} + +void BufferedDataSource::CleanupTask() { + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(!stopped_on_render_loop_); + + // Stop the watch dog. + watch_dog_timer_.Stop(); + + // We just need to stop the loader, so it stops activity. + if (loader_.get()) + loader_->Stop(); + + // Reset the parameters of the current read request. + read_callback_.reset(); + read_position_ = 0; + read_size_ = 0; + read_buffer_ = 0; + read_submitted_time_ = base::Time(); + read_attempts_ = 0; + + // Signal that stop task has finished execution. + stopped_on_render_loop_ = true; +} + +void BufferedDataSource::RestartLoadingTask() { + DCHECK(MessageLoop::current() == render_loop_); + + // This variable is set in CleanupTask(). We check this and do an early + // return. The sequence of actions which enable this conditions is: + // 1. Stop() is called from the pipeline. + // 2. ReadCallback() is called from the resource loader. + // 3. CleanupTask() is executed. + // 4. RestartLoadingTask() is executed. + if (stopped_on_render_loop_) + return; + + // If there's no outstanding read then return early. + if (!read_callback_.get()) + return; + + loader_ = CreateResourceLoader(read_position_, -1); + loader_->SetAllowDefer(!media_is_paused_); + loader_->Start( + NewCallback(this, &BufferedDataSource::PartialReadStartCallback), + NewCallback(this, &BufferedDataSource::NetworkEventCallback)); +} + +void BufferedDataSource::WatchDogTask() { + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(!stopped_on_render_loop_); + + // We only care if there is an active read request. + if (!read_callback_.get()) + return; + + DCHECK(loader_.get()); + base::TimeDelta delta = base::Time::Now() - read_submitted_time_; + if (delta < GetTimeoutMilliseconds()) + return; + + // TODO(hclam): Maybe raise an error here. But if an error is reported + // the whole pipeline may get destroyed... + if (read_attempts_ >= kReadTrials) + return; + + ++read_attempts_; + read_submitted_time_ = base::Time::Now(); + + // Stops the current loader and creates a new resource loader and + // retry the request. + loader_->Stop(); + loader_ = CreateResourceLoader(read_position_, -1); + loader_->SetAllowDefer(!media_is_paused_); + loader_->Start( + NewCallback(this, &BufferedDataSource::PartialReadStartCallback), + NewCallback(this, &BufferedDataSource::NetworkEventCallback)); +} + +void BufferedDataSource::SetPlaybackRateTask(float playback_rate) { + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(loader_.get()); + + bool previously_paused = media_is_paused_; + media_is_paused_ = (playback_rate == 0.0); + + // Disallow deferring data when we are pausing, allow deferring data + // when we resume playing. + if (previously_paused && !media_is_paused_) { + loader_->SetAllowDefer(true); + } else if (!previously_paused && media_is_paused_) { + loader_->SetAllowDefer(false); + } +} + +// This method is the place where actual read happens, |loader_| must be valid +// prior to make this method call. +void BufferedDataSource::ReadInternal() { + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(loader_.get()); + + // First we prepare the intermediate read buffer for BufferedResourceLoader + // to write to. + if (read_size_ > intermediate_read_buffer_size_) { + intermediate_read_buffer_.reset(new uint8[read_size_]); + } + + // Perform the actual read with BufferedResourceLoader. + loader_->Read(read_position_, read_size_, intermediate_read_buffer_.get(), + NewCallback(this, &BufferedDataSource::ReadCallback)); +} + +// Method to report the results of the current read request. Also reset all +// the read parameters. +void BufferedDataSource::DoneRead_Locked(int error) { + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(read_callback_.get()); + lock_.AssertAcquired(); + + if (error >= 0) { + read_callback_->RunWithParams(Tuple1(error)); + } else { + read_callback_->RunWithParams( + Tuple1(static_cast(media::DataSource::kReadError))); + } + + read_callback_.reset(); + read_position_ = 0; + read_size_ = 0; + read_buffer_ = 0; +} + +void BufferedDataSource::DoneInitialization_Locked() { + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(initialize_callback_.get()); + lock_.AssertAcquired(); + + initialize_callback_->Run(); + initialize_callback_.reset(); +} + +///////////////////////////////////////////////////////////////////////////// +// BufferedDataSource, callback methods. +// These methods are called on the render thread for the events reported by +// BufferedResourceLoader. +void BufferedDataSource::HttpInitialStartCallback(int error) { + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(loader_.get()); + + int64 instance_size = loader_->instance_size(); + bool partial_response = loader_->partial_response(); + bool success = error == net::OK; + + if (success) { + // TODO(hclam): Needs more thinking about supporting servers without range + // request or their partial response is not complete. + total_bytes_ = instance_size; + loaded_ = false; + streaming_ = (instance_size == kPositionNotSpecified) || !partial_response; + } else { + // TODO(hclam): In case of failure, we can retry several times. + loader_->Stop(); + } + + // We need to prevent calling to filter host and running the callback if + // we have received the stop signal. We need to lock down the whole callback + // method to prevent bad things from happening. The reason behind this is + // that we cannot guarantee tasks on render thread have completely stopped + // when we receive the Stop() method call. The only way to solve this is to + // let tasks on render thread to run but make sure they don't call outside + // this object when Stop() method is ever called. Locking this method is safe + // because |lock_| is only acquired in tasks on render thread. + AutoLock auto_lock(lock_); + if (stop_signal_received_) + return; + + if (!success) { + host()->SetError(media::PIPELINE_ERROR_NETWORK); + DoneInitialization_Locked(); + return; + } + + if (streaming_) { + // If the server didn't reply with an instance size, it is likely this + // is a streaming response. + host()->SetStreaming(true); + } else { + // This value governs the range that we can seek to. + // TODO(hclam): Report the correct value of buffered bytes. + host()->SetTotalBytes(total_bytes_); + host()->SetBufferedBytes(0); + } + + // Currently, only files can be used reliably w/o a network. + host()->SetLoaded(false); + DoneInitialization_Locked(); +} + +void BufferedDataSource::NonHttpInitialStartCallback(int error) { + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(loader_.get()); + + int64 instance_size = loader_->instance_size(); + bool success = error == net::OK && instance_size != kPositionNotSpecified; + + if (success) { + total_bytes_ = instance_size; + loaded_ = true; + } else { + loader_->Stop(); + } + + // We need to prevent calling to filter host and running the callback if + // we have received the stop signal. We need to lock down the whole callback + // method to prevent bad things from happening. The reason behind this is + // that we cannot guarantee tasks on render thread have completely stopped + // when we receive the Stop() method call. The only way to solve this is to + // let tasks on render thread to run but make sure they don't call outside + // this object when Stop() method is ever called. Locking this method is safe + // because |lock_| is only acquired in tasks on render thread. + AutoLock auto_lock(lock_); + if (stop_signal_received_) + return; + + if (success) { + host()->SetTotalBytes(total_bytes_); + host()->SetBufferedBytes(total_bytes_); + host()->SetLoaded(loaded_); + } else { + host()->SetError(media::PIPELINE_ERROR_NETWORK); + } + DoneInitialization_Locked(); +} + +void BufferedDataSource::PartialReadStartCallback(int error) { + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(loader_.get()); + + // This callback method is invoked after we have verified the server has + // range request capability, so as a safety guard verify again the response + // is partial. + if (error == net::OK && loader_->partial_response()) { + // Once the range request has started successfully, we can proceed with + // reading from it. + ReadInternal(); + return; + } + + // Stop the resource loader since we have received an error. + loader_->Stop(); + + // We need to prevent calling to filter host and running the callback if + // we have received the stop signal. We need to lock down the whole callback + // method to prevent bad things from happening. The reason behind this is + // that we cannot guarantee tasks on render thread have completely stopped + // when we receive the Stop() method call. So only way to solve this is to + // let tasks on render thread to run but make sure they don't call outside + // this object when Stop() method is ever called. Locking this method is + // safe because |lock_| is only acquired in tasks on render thread. + AutoLock auto_lock(lock_); + if (stop_signal_received_) + return; + DoneRead_Locked(net::ERR_INVALID_RESPONSE); +} + +void BufferedDataSource::ReadCallback(int error) { + DCHECK(MessageLoop::current() == render_loop_); + + if (error < 0) { + DCHECK(loader_.get()); + + // Stop the resource load if it failed. + loader_->Stop(); + + if (error == net::ERR_CACHE_MISS) { + render_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, &BufferedDataSource::RestartLoadingTask)); + return; + } + } + + // We need to prevent calling to filter host and running the callback if + // we have received the stop signal. We need to lock down the whole callback + // method to prevent bad things from happening. The reason behind this is + // that we cannot guarantee tasks on render thread have completely stopped + // when we receive the Stop() method call. So only way to solve this is to + // let tasks on render thread to run but make sure they don't call outside + // this object when Stop() method is ever called. Locking this method is safe + // because |lock_| is only acquired in tasks on render thread. + AutoLock auto_lock(lock_); + if (stop_signal_received_) + return; + + if (error > 0) { + // If a position error code is received, read was successful. So copy + // from intermediate read buffer to the target read buffer. + memcpy(read_buffer_, intermediate_read_buffer_.get(), error); + } + DoneRead_Locked(error); +} + +void BufferedDataSource::NetworkEventCallback() { + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(loader_.get()); + + // In case of non-HTTP request we don't need to report network events, + // so return immediately. + if (loaded_) + return; + + bool network_activity = loader_->network_activity(); + int64 buffered_last_byte_position = loader_->GetBufferedLastBytePosition(); + + // If we get an unspecified value, return immediately. + if (buffered_last_byte_position == kPositionNotSpecified) + return; + + // We need to prevent calling to filter host and running the callback if + // we have received the stop signal. We need to lock down the whole callback + // method to prevent bad things from happening. The reason behind this is + // that we cannot guarantee tasks on render thread have completely stopped + // when we receive the Stop() method call. So only way to solve this is to + // let tasks on render thread to run but make sure they don't call outside + // this object when Stop() method is ever called. Locking this method is safe + // because |lock_| is only acquired in tasks on render thread. + AutoLock auto_lock(lock_); + if (stop_signal_received_) + return; + + if (network_activity != network_activity_) { + network_activity_ = network_activity; + host()->SetNetworkActivity(network_activity); + } + host()->SetBufferedBytes(buffered_last_byte_position + 1); +} + +} // namespace webkit_glue diff --git a/webkit/glue/media/buffered_data_source.h b/webkit/glue/media/buffered_data_source.h new file mode 100644 index 0000000..0dc2115 --- /dev/null +++ b/webkit/glue/media/buffered_data_source.h @@ -0,0 +1,413 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_MEDIA_BUFFERED_DATA_SOURCE_H_ +#define WEBKIT_GLUE_MEDIA_BUFFERED_DATA_SOURCE_H_ + +#include +#include +#include + +#include "base/callback.h" +#include "base/lock.h" +#include "base/scoped_ptr.h" +#include "base/timer.h" +#include "base/condition_variable.h" +#include "googleurl/src/gurl.h" +#include "media/base/factory.h" +#include "media/base/filters.h" +#include "media/base/media_format.h" +#include "media/base/pipeline.h" +#include "media/base/seekable_buffer.h" +#include "net/base/completion_callback.h" +#include "net/base/file_stream.h" +#include "webkit/glue/media/media_resource_loader_bridge_factory.h" + +namespace webkit_glue { +///////////////////////////////////////////////////////////////////////////// +// BufferedResourceLoader +// This class works inside demuxer thread and render thread. It contains a +// resource loader bridge and does the actual resource loading. This object +// does buffering internally, it defers the resource loading if buffer is +// full and un-defers the resource loading if it is under buffered. +class BufferedResourceLoader : + public base::RefCountedThreadSafe, + public webkit_glue::ResourceLoaderBridge::Peer { + public: + typedef Callback0::Type NetworkEventCallback; + + // |bridge_factory| - Factory to create a ResourceLoaderBridge. + // |url| - URL for the resource to be loaded. + // |first_byte_position| - First byte to start loading from, -1 for not + // specified. + // |last_byte_position| - Last byte to be loaded, -1 for not specified. + BufferedResourceLoader( + webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory, + const GURL& url, + int64 first_byte_position, + int64 last_byte_position); + + // Start the resource loading with the specified URL and range. + // This method operates in asynchronous mode. Once there's a response from the + // server, success or fail |callback| is called with the result. + // |callback| is called with the following values: + // - net::OK + // The request has started successfully. + // - net::ERR_FAILED + // The request has failed because of an error with the network. + // - net::ERR_INVALID_RESPONSE + // An invalid response is received from the server. + // - (Anything else) + // An error code that indicates the request has failed. + // |event_callback| is called when the response is completed, data is + // received, the request is suspended or resumed. + virtual void Start(net::CompletionCallback* callback, + NetworkEventCallback* event_callback); + + // Stop this loader, cancels and request and release internal buffer. + virtual void Stop(); + + // Reads the specified |read_size| from |position| into |buffer| and when + // the operation is done invoke |callback| with number of bytes read or an + // error code. + // |callback| is called with the following values: + // - (Anything greater than or equal 0) + // Read was successful with the indicated number of bytes read. + // - net::ERR_FAILED + // The read has failed because of an error with the network. + // - net::ERR_CACHE_MISS + // The read was made too far away from the current buffered position. + virtual void Read(int64 position, int read_size, + uint8* buffer, net::CompletionCallback* callback); + + // Returns the position of the first byte buffered. Returns -1 if such value + // is not available. + virtual int64 GetBufferedFirstBytePosition(); + + // Returns the position of the last byte buffered. Returns -1 if such value + // is not available. + virtual int64 GetBufferedLastBytePosition(); + + // Sets whether deferring data is allowed or disallowed. + virtual void SetAllowDefer(bool is_allowed); + + // Gets the content length in bytes of the instance after this loader has been + // started. If this value is -1, then content length is unknown. + virtual int64 content_length() { return content_length_; } + + // Gets the original size of the file requested. If this value is -1, then + // the size is unknown. + virtual int64 instance_size() { return instance_size_; } + + // Returns true if the response for this loader is a partial response. + // It means a 206 response in HTTP/HTTPS protocol. + virtual bool partial_response() { return partial_response_; } + + // Returns true if network is currently active. + virtual bool network_activity() { return !completed_ && !deferred_; } + + ///////////////////////////////////////////////////////////////////////////// + // webkit_glue::ResourceLoaderBridge::Peer implementations. + virtual void OnUploadProgress(uint64 position, uint64 size) {} + virtual bool OnReceivedRedirect( + const GURL& new_url, + const webkit_glue::ResourceLoaderBridge::ResponseInfo& info, + bool* has_new_first_party_for_cookies, + GURL* new_first_party_for_cookies); + virtual void OnReceivedResponse( + const webkit_glue::ResourceLoaderBridge::ResponseInfo& info, + bool content_filtered); + virtual void OnReceivedData(const char* data, int len); + virtual void OnCompletedRequest(const URLRequestStatus& status, + const std::string& security_info); + GURL GetURLForDebugging() const { return url_; } + + protected: + friend class base::RefCountedThreadSafe; + + virtual ~BufferedResourceLoader(); + + private: + friend class BufferedResourceLoaderTest; + + // Defer the resource loading if the buffer is full. + void EnableDeferIfNeeded(); + + // Disable defer loading if we are under-buffered. + void DisableDeferIfNeeded(); + + // Returns true if the current read request can be fulfilled by what is in + // the buffer. + bool CanFulfillRead(); + + // Returns true if the current read request will be fulfilled in the future. + bool WillFulfillRead(); + + // Method that does the actual read and calls the |read_callbac_|, assuming + // the request range is in |buffer_|. + void ReadInternal(); + + // If we have made a range request, verify the response from the server. + bool VerifyPartialResponse(const ResourceLoaderBridge::ResponseInfo& info); + + // Done with read. Invokes the read callback and reset parameters for the + // read request. + void DoneRead(int error); + + // Done with start. Invokes the start callback and reset it. + void DoneStart(int error); + + // Calls |event_callback_| in terms of a network event. + void NotifyNetworkEvent(); + + bool HasPendingRead() { return read_callback_.get() != NULL; } + + // A sliding window of buffer. + scoped_ptr buffer_; + + // True if resource loading was deferred. + bool deferred_; + + // True if resource loader is allowed to defer, false otherwise. + bool defer_allowed_; + + // True if resource loading has completed. + bool completed_; + + // True if a range request was made. + bool range_requested_; + + // True if response data received is a partial range. + bool partial_response_; + + webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory_; + GURL url_; + int64 first_byte_position_; + int64 last_byte_position_; + + // Callback method that listens to network events. + scoped_ptr event_callback_; + + // Members used during request start. + scoped_ptr start_callback_; + scoped_ptr bridge_; + int64 offset_; + int64 content_length_; + int64 instance_size_; + + // Members used during a read operation. They should be reset after each + // read has completed or failed. + scoped_ptr read_callback_; + int64 read_position_; + int read_size_; + uint8* read_buffer_; + + // Offsets of the requested first byte and last byte in |buffer_|. They are + // written by VerifyRead(). + int first_offset_; + int last_offset_; + + DISALLOW_COPY_AND_ASSIGN(BufferedResourceLoader); +}; + +class BufferedDataSource : public media::DataSource { + public: + // Methods called from pipeline thread + // Static methods for creating this class. + static media::FilterFactory* CreateFactory( + MessageLoop* message_loop, + webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory) { + return new media::FilterFactoryImpl2< + BufferedDataSource, + MessageLoop*, + webkit_glue::MediaResourceLoaderBridgeFactory*>( + message_loop, bridge_factory); + } + + // media::FilterFactoryImpl2 implementation. + static bool IsMediaFormatSupported( + const media::MediaFormat& media_format); + + // media::MediaFilter implementation. + virtual void Initialize(const std::string& url, + media::FilterCallback* callback); + virtual void Stop(media::FilterCallback* callback); + virtual void SetPlaybackRate(float playback_rate); + + // media::DataSource implementation. + // Called from demuxer thread. + virtual void Read(int64 position, size_t size, + uint8* data, + media::DataSource::ReadCallback* read_callback); + virtual bool GetSize(int64* size_out); + virtual bool IsStreaming(); + + const media::MediaFormat& media_format() { + return media_format_; + } + + protected: + BufferedDataSource( + MessageLoop* render_loop, + webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory); + virtual ~BufferedDataSource(); + + // A factory method to create a BufferedResourceLoader based on the read + // parameters. We can override this file to object a mock + // BufferedResourceLoader for testing. + virtual BufferedResourceLoader* CreateResourceLoader( + int64 first_byte_position, int64 last_byte_position); + + // Gets the number of milliseconds to declare a request timeout since + // the request was made. This method is made virtual so as to inject a + // different number for testing purpose. + virtual base::TimeDelta GetTimeoutMilliseconds(); + + private: + friend class media::FilterFactoryImpl2< + BufferedDataSource, + MessageLoop*, + webkit_glue::MediaResourceLoaderBridgeFactory*>; + + // Posted to perform initialization on render thread and start resource + // loading. + void InitializeTask(); + + // Task posted to perform actual reading on the render thread. + void ReadTask(int64 position, int read_size, uint8* read_buffer, + media::DataSource::ReadCallback* read_callback); + + // Task posted when Stop() is called. Stops |watch_dog_timer_| and + // |loader_|, reset Read() variables, and set |stopped_on_render_loop_| + // to signal any remaining tasks to stop. + void CleanupTask(); + + // Restart resource loading on render thread. + void RestartLoadingTask(); + + // This task monitors the current active read request. If the current read + // request has timed out, this task will destroy the current loader and + // creates a new one to accommodate the read request. + void WatchDogTask(); + + // This task uses the current playback rate with the previous playback rate + // to determine whether we are going from pause to play and play to pause, + // and signals the buffered resource loader accordingly. + void SetPlaybackRateTask(float playback_rate); + + // The method that performs actual read. This method can only be executed on + // the render thread. + void ReadInternal(); + + // Calls |read_callback_| and reset all read parameters. + void DoneRead_Locked(int error); + + // Calls |initialize_callback_| and reset it. + void DoneInitialization_Locked(); + + // Callback method for |loader_| if URL for the resource requested is using + // HTTP protocol. This method is called when response for initial request is + // received. + void HttpInitialStartCallback(int error); + + // Callback method for |loader_| if URL for the resource requested is using + // a non-HTTP protocol, e.g. local files. This method is called when response + // for initial request is received. + void NonHttpInitialStartCallback(int error); + + // Callback method to be passed to BufferedResourceLoader during range + // request. Once a resource request has started, this method will be called + // with the error code. This method will be executed on the thread + // BufferedResourceLoader lives, i.e. render thread. + void PartialReadStartCallback(int error); + + // Callback method for making a read request to BufferedResourceLoader. + // If data arrives or the request has failed, this method is called with + // the error code or the number of bytes read. + void ReadCallback(int error); + + // Callback method when a network event is received. + void NetworkEventCallback(); + + media::MediaFormat media_format_; + + // URL of the resource requested. + GURL url_; + + // Members for total bytes of the requested object. It is written once on + // render thread but may be read from any thread. However reading of this + // member is guaranteed to happen after it is first written, so we don't + // need to protect it. + int64 total_bytes_; + + // True if this data source is considered loaded. + bool loaded_; + + // This value will be true if this data source can only support streaming. + // i.e. range request is not supported. + bool streaming_; + + // A factory object to produce ResourceLoaderBridge. + scoped_ptr bridge_factory_; + + // A resource loader for the media resource. + scoped_refptr loader_; + + // True if network is active. + bool network_activity_; + + // Callback method from the pipeline for initialization. + scoped_ptr initialize_callback_; + + // Read parameters received from the Read() method call. + scoped_ptr read_callback_; + int64 read_position_; + int read_size_; + uint8* read_buffer_; + base::Time read_submitted_time_; + int read_attempts_; + + // This buffer is intermediate, we use it for BufferedResourceLoader to write + // to. And when read in BufferedResourceLoader is done, we copy data from + // this buffer to |read_buffer_|. The reason for an additional copy is that + // we don't own |read_buffer_|. But since the read operation is asynchronous, + // |read_buffer| can be destroyed at any time, so we only copy into + // |read_buffer| in the final step when it is safe. + // Memory is allocated for this member during initialization of this object + // because we want buffer to be passed into BufferedResourceLoader to be + // always non-null. And by initializing this member with a default size we can + // avoid creating zero-sized buffered if the first read has zero size. + scoped_array intermediate_read_buffer_; + int intermediate_read_buffer_size_; + + // The message loop of the render thread. + MessageLoop* render_loop_; + + // Protects |stopped_|. + Lock lock_; + + // Stop signal to suppressing activities. This variable is set on the pipeline + // thread and read from the render thread. + bool stop_signal_received_; + + // This variable is set by CleanupTask() that indicates this object is stopped + // on the render thread. + bool stopped_on_render_loop_; + + // This variable is true when we are in a paused state and false when we + // are in a playing state. + bool media_is_paused_; + + // This timer is to run the WatchDogTask repeatedly. We use a timer instead + // of doing PostDelayedTask() reduce the extra reference held by the message + // loop. The RepeatingTimer does PostDelayedTask() internally, by using it + // the message loop doesn't hold a reference for the watch dog task. + base::RepeatingTimer watch_dog_timer_; + + DISALLOW_COPY_AND_ASSIGN(BufferedDataSource); +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_MEDIA_BUFFERED_DATA_SOURCE_H_ diff --git a/webkit/glue/media/buffered_data_source_unittest.cc b/webkit/glue/media/buffered_data_source_unittest.cc new file mode 100644 index 0000000..ce42437 --- /dev/null +++ b/webkit/glue/media/buffered_data_source_unittest.cc @@ -0,0 +1,934 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/callback.h" +#include "base/format_macros.h" +#include "base/string_util.h" +#include "media/base/filters.h" +#include "media/base/mock_filter_host.h" +#include "media/base/mock_filters.h" +#include "net/base/net_errors.h" +#include "net/http/http_response_headers.h" +#include "webkit/glue/media/buffered_data_source.h" +#include "webkit/glue/media/mock_media_resource_loader_bridge_factory.h" +#include "webkit/glue/mock_resource_loader_bridge.h" + +using ::testing::_; +using ::testing::Assign; +using ::testing::DeleteArg; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::InvokeWithoutArgs; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::SetArgumentPointee; +using ::testing::StrictMock; +using ::testing::WithArgs; + +namespace { + +const char* kHttpUrl = "http://test"; +const char* kFileUrl = "file://test"; +const int kDataSize = 1024; + +enum NetworkState { + NONE, + LOADED, + LOADING +}; + +} // namespace + +namespace webkit_glue { + +// Submit a request completed event to the resource loader due to request +// being canceled. Pretending the event is from external. +ACTION_P(RequestCanceled, loader) { + URLRequestStatus status; + status.set_status(URLRequestStatus::CANCELED); + status.set_os_error(net::ERR_ABORTED); + loader->OnCompletedRequest(status, ""); +} + +class BufferedResourceLoaderTest : public testing::Test { + public: + BufferedResourceLoaderTest() { + bridge_.reset(new StrictMock()); + + for (int i = 0; i < kDataSize; ++i) + data_[i] = i; + } + + ~BufferedResourceLoaderTest() { + if (bridge_.get()) + EXPECT_CALL(*bridge_, OnDestroy()); + EXPECT_CALL(bridge_factory_, OnDestroy()); + } + + void Initialize(const char* url, int first_position, int last_position) { + gurl_ = GURL(url); + first_position_ = first_position; + last_position_ = last_position; + + loader_ = new BufferedResourceLoader(&bridge_factory_, gurl_, + first_position_, last_position_); + EXPECT_EQ(gurl_.spec(), loader_->GetURLForDebugging().spec()); + } + + void SetLoaderBuffer(size_t forward_capacity, size_t backward_capacity) { + loader_->buffer_.reset( + new media::SeekableBuffer(backward_capacity, forward_capacity)); + } + + void Start() { + InSequence s; + EXPECT_CALL(bridge_factory_, + CreateBridge(gurl_, _, first_position_, last_position_)) + .WillOnce(Return(bridge_.get())); + EXPECT_CALL(*bridge_, Start(loader_.get())); + loader_->Start( + NewCallback(this, &BufferedResourceLoaderTest::StartCallback), + NewCallback(this, &BufferedResourceLoaderTest::NetworkCallback)); + } + + void FullResponse(int64 instance_size) { + EXPECT_CALL(*this, StartCallback(net::OK)); + ResourceLoaderBridge::ResponseInfo info; + std::string header = StringPrintf("HTTP/1.1 200 OK\n" + "Content-Length: %" PRId64, + instance_size); + replace(header.begin(), header.end(), '\n', '\0'); + info.headers = new net::HttpResponseHeaders(header); + info.content_length = instance_size; + loader_->OnReceivedResponse(info, false); + EXPECT_EQ(instance_size, loader_->content_length()); + EXPECT_EQ(instance_size, loader_->instance_size()); + EXPECT_FALSE(loader_->partial_response()); + } + + void PartialResponse(int64 first_position, int64 last_position, + int64 instance_size) { + EXPECT_CALL(*this, StartCallback(net::OK)); + int64 content_length = last_position - first_position + 1; + ResourceLoaderBridge::ResponseInfo info; + std::string header = StringPrintf("HTTP/1.1 206 Partial Content\n" + "Content-Range: bytes " + "%" PRId64 "-%" PRId64 "/%" PRId64, + first_position, + last_position, + instance_size); + replace(header.begin(), header.end(), '\n', '\0'); + info.headers = new net::HttpResponseHeaders(header); + info.content_length = content_length; + loader_->OnReceivedResponse(info, false); + EXPECT_EQ(content_length, loader_->content_length()); + EXPECT_EQ(instance_size, loader_->instance_size()); + EXPECT_TRUE(loader_->partial_response()); + } + + void StopWhenLoad() { + InSequence s; + EXPECT_CALL(*bridge_, Cancel()) + .WillOnce(RequestCanceled(loader_)); + EXPECT_CALL(*bridge_, OnDestroy()) + .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge)); + loader_->Stop(); + } + + void ReleaseBridge() { + ignore_result(bridge_.release()); + } + + // Helper method to write to |loader_| from |data_|. + void WriteLoader(int position, int size) { + EXPECT_CALL(*this, NetworkCallback()) + .RetiresOnSaturation(); + loader_->OnReceivedData(reinterpret_cast(data_ + position), size); + } + + // Helper method to read from |loader_|. + void ReadLoader(int64 position, int size, uint8* buffer) { + loader_->Read(position, size, buffer, + NewCallback(this, &BufferedResourceLoaderTest::ReadCallback)); + } + + // Verifis that data in buffer[0...size] is equal to data_[pos...pos+size]. + void VerifyBuffer(uint8* buffer, int pos, int size) { + EXPECT_EQ(0, memcmp(buffer, data_ + pos, size)); + } + + // Helper method to disallow deferring in |loader_|. + void DisallowLoaderDefer() { + if (loader_->deferred_) { + EXPECT_CALL(*bridge_, SetDefersLoading(false)); + EXPECT_CALL(*this, NetworkCallback()); + } + loader_->SetAllowDefer(false); + } + + // Helper method to allow deferring in |loader_|. + void AllowLoaderDefer() { + loader_->SetAllowDefer(true); + } + + MOCK_METHOD1(StartCallback, void(int error)); + MOCK_METHOD1(ReadCallback, void(int error)); + MOCK_METHOD0(NetworkCallback, void()); + + protected: + GURL gurl_; + int64 first_position_; + int64 last_position_; + + scoped_refptr loader_; + StrictMock bridge_factory_; + scoped_ptr > bridge_; + + uint8 data_[kDataSize]; + + private: + DISALLOW_COPY_AND_ASSIGN(BufferedResourceLoaderTest); +}; + +TEST_F(BufferedResourceLoaderTest, StartStop) { + Initialize(kHttpUrl, -1, -1); + Start(); + StopWhenLoad(); +} + +// Tests that HTTP header is missing in the response. +TEST_F(BufferedResourceLoaderTest, MissingHttpHeader) { + Initialize(kHttpUrl, -1, -1); + Start(); + + EXPECT_CALL(*this, StartCallback(net::ERR_INVALID_RESPONSE)); + EXPECT_CALL(*bridge_, Cancel()) + .WillOnce(RequestCanceled(loader_)); + EXPECT_CALL(*bridge_, OnDestroy()) + .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge)); + + ResourceLoaderBridge::ResponseInfo info; + loader_->OnReceivedResponse(info, false); +} + +// Tests that a bad HTTP response is recived, e.g. file not found. +TEST_F(BufferedResourceLoaderTest, BadHttpResponse) { + Initialize(kHttpUrl, -1, -1); + Start(); + + EXPECT_CALL(*this, StartCallback(net::ERR_FAILED)); + EXPECT_CALL(*bridge_, Cancel()) + .WillOnce(RequestCanceled(loader_)); + EXPECT_CALL(*bridge_, OnDestroy()) + .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge)); + + ResourceLoaderBridge::ResponseInfo info; + info.headers = new net::HttpResponseHeaders("HTTP/1.1 404 Not Found\n"); + loader_->OnReceivedResponse(info, false); +} + +// Tests that partial content is requested but not fulfilled. +TEST_F(BufferedResourceLoaderTest, NotPartialResponse) { + Initialize(kHttpUrl, 100, -1); + Start(); + FullResponse(1024); + StopWhenLoad(); +} + +// Tests that a 200 response is received. +TEST_F(BufferedResourceLoaderTest, FullResponse) { + Initialize(kHttpUrl, -1, -1); + Start(); + FullResponse(1024); + StopWhenLoad(); +} + +// Tests that a partial content response is received. +TEST_F(BufferedResourceLoaderTest, PartialResponse) { + Initialize(kHttpUrl, 100, 200); + Start(); + PartialResponse(100, 200, 1024); + StopWhenLoad(); +} + +// Tests that an invalid partial response is received. +TEST_F(BufferedResourceLoaderTest, InvalidPartialResponse) { + Initialize(kHttpUrl, 0, 10); + Start(); + + EXPECT_CALL(*this, StartCallback(net::ERR_INVALID_RESPONSE)); + EXPECT_CALL(*bridge_, Cancel()) + .WillOnce(RequestCanceled(loader_)); + EXPECT_CALL(*bridge_, OnDestroy()) + .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge)); + + ResourceLoaderBridge::ResponseInfo info; + std::string header = StringPrintf("HTTP/1.1 206 Partial Content\n" + "Content-Range: bytes %d-%d/%d", + 1, 10, 1024); + replace(header.begin(), header.end(), '\n', '\0'); + info.headers = new net::HttpResponseHeaders(header); + info.content_length = 10; + loader_->OnReceivedResponse(info, false); +} + +// Tests the logic of sliding window for data buffering and reading. +TEST_F(BufferedResourceLoaderTest, BufferAndRead) { + Initialize(kHttpUrl, 10, 29); + Start(); + PartialResponse(10, 29, 30); + + uint8 buffer[10]; + InSequence s; + + // Writes 10 bytes and read them back. + WriteLoader(10, 10); + EXPECT_CALL(*this, ReadCallback(10)); + ReadLoader(10, 10, buffer); + VerifyBuffer(buffer, 10, 10); + + // Writes 10 bytes and read 2 times. + WriteLoader(20, 10); + EXPECT_CALL(*this, ReadCallback(5)); + ReadLoader(20, 5, buffer); + VerifyBuffer(buffer, 20, 5); + EXPECT_CALL(*this, ReadCallback(5)); + ReadLoader(25, 5, buffer); + VerifyBuffer(buffer, 25, 5); + + // Read backward within buffer. + EXPECT_CALL(*this, ReadCallback(10)); + ReadLoader(10, 10, buffer); + VerifyBuffer(buffer, 10, 10); + + // Read backward outside buffer. + EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS)); + ReadLoader(9, 10, buffer); + + // Response has completed. + EXPECT_CALL(*this, NetworkCallback()); + EXPECT_CALL(*bridge_, OnDestroy()) + .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge)); + URLRequestStatus status; + status.set_status(URLRequestStatus::SUCCESS); + loader_->OnCompletedRequest(status, ""); + + // Try to read 10 from position 25 will just return with 5 bytes. + EXPECT_CALL(*this, ReadCallback(5)); + ReadLoader(25, 10, buffer); + VerifyBuffer(buffer, 25, 5); + + // Try to read outside buffered range after request has completed. + EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS)); + ReadLoader(5, 10, buffer); + + // Try to read beyond the instance size. + EXPECT_CALL(*this, ReadCallback(0)); + ReadLoader(30, 10, buffer); +} + +TEST_F(BufferedResourceLoaderTest, ReadOutsideBuffer) { + Initialize(kHttpUrl, 10, 0x00FFFFFF); + Start(); + PartialResponse(10, 0x00FFFFFF, 0x01000000); + + uint8 buffer[10]; + InSequence s; + + // Read very far aheard will get a cache miss. + EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS)); + ReadLoader(0x00FFFFFF, 1, buffer); + + // The following call will not call ReadCallback() because it is waiting for + // data to arrive. + ReadLoader(10, 10, buffer); + + // Writing to loader will fulfill the read request. + EXPECT_CALL(*this, ReadCallback(10)); + WriteLoader(10, 20); + VerifyBuffer(buffer, 10, 10); + + // The following call cannot be fulfilled now. + ReadLoader(25, 10, buffer); + + EXPECT_CALL(*this, ReadCallback(5)); + EXPECT_CALL(*this, NetworkCallback()); + EXPECT_CALL(*bridge_, OnDestroy()) + .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge)); + URLRequestStatus status; + status.set_status(URLRequestStatus::SUCCESS); + loader_->OnCompletedRequest(status, ""); +} + +TEST_F(BufferedResourceLoaderTest, RequestFailedWhenRead) { + Initialize(kHttpUrl, 10, 29); + Start(); + PartialResponse(10, 29, 30); + + uint8 buffer[10]; + InSequence s; + + ReadLoader(10, 10, buffer); + EXPECT_CALL(*this, ReadCallback(net::ERR_FAILED)); + EXPECT_CALL(*this, NetworkCallback()); + EXPECT_CALL(*bridge_, OnDestroy()) + .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge)); + URLRequestStatus status; + status.set_status(URLRequestStatus::FAILED); + loader_->OnCompletedRequest(status, ""); +} + +// Tests the logic of caching data to disk when media is paused. +TEST_F(BufferedResourceLoaderTest, AllowDefer_NoDataReceived) { + Initialize(kHttpUrl, 10, 99); + SetLoaderBuffer(10, 20); + Start(); + PartialResponse(10, 99, 100); + + // Start in undeferred state, then disallow defer, then allow defer + // without receiving data in between. + DisallowLoaderDefer(); + AllowLoaderDefer(); + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, AllowDefer_ReadSameWindow) { + Initialize(kHttpUrl, 10, 99); + SetLoaderBuffer(10, 20); + Start(); + PartialResponse(10, 99, 100); + + uint8 buffer[10]; + + // Start in undeferred state, disallow defer, receive data but don't shift + // buffer window, then allow defer and read. + DisallowLoaderDefer(); + WriteLoader(10, 10); + AllowLoaderDefer(); + + EXPECT_CALL(*this, ReadCallback(10)); + ReadLoader(10, 10, buffer); + VerifyBuffer(buffer, 10, 10); + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, AllowDefer_ReadPastWindow) { + Initialize(kHttpUrl, 10, 99); + SetLoaderBuffer(10, 20); + Start(); + PartialResponse(10, 99, 100); + + uint8 buffer[10]; + + // Not deferred, disallow defer, received data and shift buffer window, + // allow defer, then read in area outside of buffer window. + DisallowLoaderDefer(); + WriteLoader(10, 10); + WriteLoader(20, 50); + AllowLoaderDefer(); + + EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS)); + ReadLoader(10, 10, buffer); + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, AllowDefer_DeferredNoDataReceived) { + Initialize(kHttpUrl, 10, 99); + SetLoaderBuffer(10, 20); + Start(); + PartialResponse(10, 99, 100); + + uint8 buffer[10]; + + // Start in deferred state, then disallow defer, receive no data, and + // allow defer and read. + EXPECT_CALL(*bridge_, SetDefersLoading(true)); + EXPECT_CALL(*this, NetworkCallback()); + WriteLoader(10, 40); + + DisallowLoaderDefer(); + AllowLoaderDefer(); + + EXPECT_CALL(*this, ReadCallback(10)); + ReadLoader(20, 10, buffer); + VerifyBuffer(buffer, 20, 10); + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, AllowDefer_DeferredReadSameWindow) { + Initialize(kHttpUrl, 10, 99); + SetLoaderBuffer(10, 20); + Start(); + PartialResponse(10, 99, 100); + + uint8 buffer[10]; + + // Start in deferred state, disallow defer, receive data and shift buffer + // window, allow defer, and read in a place that's still in the window. + EXPECT_CALL(*bridge_, SetDefersLoading(true)); + EXPECT_CALL(*this, NetworkCallback()); + WriteLoader(10, 30); + + DisallowLoaderDefer(); + WriteLoader(40, 5); + AllowLoaderDefer(); + + EXPECT_CALL(*this, ReadCallback(10)); + ReadLoader(20, 10, buffer); + VerifyBuffer(buffer, 20, 10); + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, AllowDefer_DeferredReadPastWindow) { + Initialize(kHttpUrl, 10, 99); + SetLoaderBuffer(10, 20); + Start(); + PartialResponse(10, 99, 100); + + uint8 buffer[10]; + + // Start in deferred state, disallow defer, receive data and shift buffer + // window, allow defer, and read outside of the buffer window. + EXPECT_CALL(*bridge_, SetDefersLoading(true)); + EXPECT_CALL(*this, NetworkCallback()); + WriteLoader(10, 40); + + DisallowLoaderDefer(); + WriteLoader(50, 20); + WriteLoader(70, 40); + AllowLoaderDefer(); + + EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS)); + ReadLoader(20, 5, buffer); + StopWhenLoad(); +} + +// TODO(hclam): add unit test for defer loading. + +class MockBufferedResourceLoader : public BufferedResourceLoader { + public: + MockBufferedResourceLoader() : BufferedResourceLoader(NULL, GURL(), 0, 0) { + } + + MOCK_METHOD2(Start, void(net::CompletionCallback* read_callback, + NetworkEventCallback* network_callback)); + MOCK_METHOD0(Stop, void()); + MOCK_METHOD4(Read, void(int64 position, int read_size, uint8* buffer, + net::CompletionCallback* callback)); + MOCK_METHOD0(content_length, int64()); + MOCK_METHOD0(instance_size, int64()); + MOCK_METHOD0(partial_response, bool()); + MOCK_METHOD0(network_activity, bool()); + MOCK_METHOD0(GetBufferedFirstBytePosition, int64()); + MOCK_METHOD0(GetBufferedLastBytePosition, int64()); + + protected: + ~MockBufferedResourceLoader() {} + + DISALLOW_COPY_AND_ASSIGN(MockBufferedResourceLoader); +}; + +// A mock BufferedDataSource to inject mock BufferedResourceLoader through +// CreateResourceLoader() method. +class MockBufferedDataSource : public BufferedDataSource { + public: + // Static methods for creating this class. + static media::FilterFactory* CreateFactory( + MessageLoop* message_loop, + MediaResourceLoaderBridgeFactory* bridge_factory) { + return new media::FilterFactoryImpl2< + MockBufferedDataSource, + MessageLoop*, + MediaResourceLoaderBridgeFactory*>(message_loop, + bridge_factory); + } + + virtual base::TimeDelta GetTimeoutMilliseconds() { + // It is 100 ms because we don't want the test to run too long. + return base::TimeDelta::FromMilliseconds(100); + } + + MOCK_METHOD2(CreateResourceLoader, BufferedResourceLoader*( + int64 first_position, int64 last_position)); + + protected: + MockBufferedDataSource( + MessageLoop* message_loop, MediaResourceLoaderBridgeFactory* factory) + : BufferedDataSource(message_loop, factory) { + } + + private: + friend class media::FilterFactoryImpl2< + MockBufferedDataSource, + MessageLoop*, + MediaResourceLoaderBridgeFactory*>; + + DISALLOW_COPY_AND_ASSIGN(MockBufferedDataSource); +}; + +class BufferedDataSourceTest : public testing::Test { + public: + BufferedDataSourceTest() { + message_loop_ = MessageLoop::current(); + bridge_factory_.reset( + new StrictMock()); + factory_ = MockBufferedDataSource::CreateFactory(message_loop_, + bridge_factory_.get()); + + // Prepare test data. + for (size_t i = 0; i < sizeof(data_); ++i) { + data_[i] = i; + } + } + + virtual ~BufferedDataSourceTest() { + if (data_source_) { + // Release the bridge factory because we don't own it. + // Expects bridge factory to be destroyed along with data source. + EXPECT_CALL(*bridge_factory_, OnDestroy()) + .WillOnce(Invoke(this, + &BufferedDataSourceTest::ReleaseBridgeFactory)); + } + } + + void InitializeDataSource(const char* url, int error, + bool partial_response, int64 instance_size, + NetworkState networkState) { + // Saves the url first. + gurl_ = GURL(url); + + media::MediaFormat url_format; + url_format.SetAsString(media::MediaFormat::kMimeType, + media::mime_type::kURL); + url_format.SetAsString(media::MediaFormat::kURL, url); + data_source_ = factory_->Create(url_format); + CHECK(data_source_); + + // There is no need to provide a message loop to data source. + data_source_->set_host(&host_); + + // Creates the mock loader to be injected. + loader_ = new StrictMock(); + + bool loaded = networkState == LOADED; + { + InSequence s; + EXPECT_CALL(*data_source_, CreateResourceLoader(_, _)) + .WillOnce(Return(loader_.get())); + + // The initial response loader will be started. + EXPECT_CALL(*loader_, Start(NotNull(), NotNull())) + .WillOnce( + DoAll(Assign(&error_, error), + Invoke(this, + &BufferedDataSourceTest::InvokeStartCallback))); + } + + StrictMock callback; + EXPECT_CALL(*loader_, instance_size()) + .WillRepeatedly(Return(instance_size)); + EXPECT_CALL(*loader_, partial_response()) + .WillRepeatedly(Return(partial_response)); + if (error == net::OK) { + // Expected loaded or not. + EXPECT_CALL(host_, SetLoaded(loaded)); + + // TODO(hclam): The condition for streaming needs to be adjusted. + if (instance_size != -1 && (loaded || partial_response)) { + EXPECT_CALL(host_, SetTotalBytes(instance_size)); + if (loaded) + EXPECT_CALL(host_, SetBufferedBytes(instance_size)); + else + EXPECT_CALL(host_, SetBufferedBytes(0)); + } else { + EXPECT_CALL(host_, SetStreaming(true)); + } + + EXPECT_CALL(callback, OnFilterCallback()); + EXPECT_CALL(callback, OnCallbackDestroyed()); + } else { + EXPECT_CALL(host_, SetError(media::PIPELINE_ERROR_NETWORK)); + EXPECT_CALL(*loader_, Stop()); + EXPECT_CALL(callback, OnFilterCallback()); + EXPECT_CALL(callback, OnCallbackDestroyed()); + } + + // Actual initialization of the data source. + data_source_->Initialize(url, callback.NewCallback()); + message_loop_->RunAllPending(); + + if (error == net::OK) { + // Verify the size of the data source. + int64 size; + if (instance_size != -1 && (loaded || partial_response)) { + EXPECT_TRUE(data_source_->GetSize(&size)); + EXPECT_EQ(instance_size, size); + } else { + EXPECT_TRUE(data_source_->IsStreaming()); + } + } + } + + void StopDataSource() { + if (loader_) { + InSequence s; + EXPECT_CALL(*loader_, Stop()); + } + + StrictMock callback; + EXPECT_CALL(callback, OnFilterCallback()); + EXPECT_CALL(callback, OnCallbackDestroyed()); + data_source_->Stop(callback.NewCallback()); + message_loop_->RunAllPending(); + } + + void ReleaseBridgeFactory() { + ignore_result(bridge_factory_.release()); + } + + void InvokeStartCallback( + net::CompletionCallback* callback, + BufferedResourceLoader::NetworkEventCallback* network_callback) { + callback->RunWithParams(Tuple1(error_)); + delete callback; + // TODO(hclam): Save this callback. + delete network_callback; + } + + void InvokeReadCallback(int64 position, int size, uint8* buffer, + net::CompletionCallback* callback) { + if (error_ > 0) + memcpy(buffer, data_ + static_cast(position), error_); + callback->RunWithParams(Tuple1(error_)); + delete callback; + } + + void ReadDataSourceHit(int64 position, int size, int read_size) { + EXPECT_TRUE(loader_); + + InSequence s; + // Expect the read is delegated to the resource loader. + EXPECT_CALL(*loader_, Read(position, size, NotNull(), NotNull())) + .WillOnce(DoAll(Assign(&error_, read_size), + Invoke(this, + &BufferedDataSourceTest::InvokeReadCallback))); + + // The read has succeeded, so read callback will be called. + EXPECT_CALL(*this, ReadCallback(read_size)); + + data_source_->Read( + position, size, buffer_, + NewCallback(this, &BufferedDataSourceTest::ReadCallback)); + message_loop_->RunAllPending(); + + // Make sure data is correct. + EXPECT_EQ(0, + memcmp(buffer_, data_ + static_cast(position), read_size)); + } + + void ReadDataSourceMiss(int64 position, int size) { + EXPECT_TRUE(loader_); + + // 1. Reply with a cache miss for the read. + { + InSequence s; + EXPECT_CALL(*loader_, Read(position, size, NotNull(), NotNull())) + .WillOnce(DoAll(Assign(&error_, net::ERR_CACHE_MISS), + Invoke(this, + &BufferedDataSourceTest::InvokeReadCallback))); + EXPECT_CALL(*loader_, Stop()); + } + + // 2. Then the current loader will be stop and destroyed. + StrictMock *new_loader = + new StrictMock(); + EXPECT_CALL(*data_source_, CreateResourceLoader(position, -1)) + .WillOnce(Return(new_loader)); + + // 3. Then the new loader will be started. + EXPECT_CALL(*new_loader, Start(NotNull(), NotNull())) + .WillOnce(DoAll(Assign(&error_, net::OK), + Invoke(this, + &BufferedDataSourceTest::InvokeStartCallback))); + EXPECT_CALL(*new_loader, partial_response()) + .WillRepeatedly(Return(loader_->partial_response())); + + // 4. Then again a read request is made to the new loader. + EXPECT_CALL(*new_loader, Read(position, size, NotNull(), NotNull())) + .WillOnce(DoAll(Assign(&error_, size), + Invoke(this, + &BufferedDataSourceTest::InvokeReadCallback))); + + EXPECT_CALL(*this, ReadCallback(size)); + + data_source_->Read( + position, size, buffer_, + NewCallback(this, &BufferedDataSourceTest::ReadCallback)); + message_loop_->RunAllPending(); + + // Make sure data is correct. + EXPECT_EQ(0, memcmp(buffer_, data_ + static_cast(position), size)); + + loader_ = new_loader; + } + + void ReadDataSourceFailed(int64 position, int size, int error) { + EXPECT_TRUE(loader_); + + // 1. Expect the read is delegated to the resource loader. + EXPECT_CALL(*loader_, Read(position, size, NotNull(), NotNull())) + .WillOnce(DoAll(Assign(&error_, error), + Invoke(this, + &BufferedDataSourceTest::InvokeReadCallback))); + + // 2. Host will then receive an error. + EXPECT_CALL(*loader_, Stop()); + + // 3. The read has failed, so read callback will be called. + EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError)); + + data_source_->Read( + position, size, buffer_, + NewCallback(this, &BufferedDataSourceTest::ReadCallback)); + + message_loop_->RunAllPending(); + } + + void ReadDataSourceTimesOut(int64 position, int size) { + // 1. Drop the request and let it times out. + { + InSequence s; + EXPECT_CALL(*loader_, Read(position, size, NotNull(), NotNull())) + .WillOnce(DeleteArg<3>()); + EXPECT_CALL(*loader_, Stop()); + } + + // 2. Then the current loader will be stop and destroyed. + StrictMock *new_loader = + new StrictMock(); + EXPECT_CALL(*data_source_, CreateResourceLoader(position, -1)) + .WillOnce(Return(new_loader)); + + // 3. Then the new loader will be started and respond to queries about + // whether this is a partial response using the value of the previous + // loader. + EXPECT_CALL(*new_loader, Start(NotNull(), NotNull())) + .WillOnce(DoAll(Assign(&error_, net::OK), + Invoke(this, + &BufferedDataSourceTest::InvokeStartCallback))); + EXPECT_CALL(*new_loader, partial_response()) + .WillRepeatedly(Return(loader_->partial_response())); + + // 4. Then again a read request is made to the new loader. + EXPECT_CALL(*new_loader, Read(position, size, NotNull(), NotNull())) + .WillOnce(DoAll(Assign(&error_, size), + Invoke(this, + &BufferedDataSourceTest::InvokeReadCallback), + InvokeWithoutArgs(message_loop_, + &MessageLoop::Quit))); + + EXPECT_CALL(*this, ReadCallback(size)); + + data_source_->Read( + position, size, buffer_, + NewCallback(this, &BufferedDataSourceTest::ReadCallback)); + + // This blocks the current thread until the watch task is executed and + // triggers a read callback to quit this message loop. + message_loop_->Run(); + + // Make sure data is correct. + EXPECT_EQ(0, memcmp(buffer_, data_ + static_cast(position), size)); + + loader_ = new_loader; + } + + MOCK_METHOD1(ReadCallback, void(size_t size)); + + scoped_ptr > + bridge_factory_; + scoped_refptr > loader_; + scoped_refptr data_source_; + scoped_refptr factory_; + + StrictMock host_; + GURL gurl_; + MessageLoop* message_loop_; + + int error_; + uint8 buffer_[1024]; + uint8 data_[1024]; + + private: + DISALLOW_COPY_AND_ASSIGN(BufferedDataSourceTest); +}; + +TEST_F(BufferedDataSourceTest, InitializationSuccess) { + InitializeDataSource(kHttpUrl, net::OK, true, 1024, LOADING); + StopDataSource(); +} + +TEST_F(BufferedDataSourceTest, InitiailizationFailed) { + InitializeDataSource(kHttpUrl, net::ERR_FILE_NOT_FOUND, false, 0, NONE); + StopDataSource(); +} + +TEST_F(BufferedDataSourceTest, MissingContentLength) { + InitializeDataSource(kHttpUrl, net::OK, true, -1, LOADING); + StopDataSource(); +} + +TEST_F(BufferedDataSourceTest, RangeRequestNotSupported) { + InitializeDataSource(kHttpUrl, net::OK, false, 1024, LOADING); + StopDataSource(); +} + +TEST_F(BufferedDataSourceTest, + MissingContentLengthAndRangeRequestNotSupported) { + InitializeDataSource(kHttpUrl, net::OK, false, -1, LOADING); + StopDataSource(); +} + +TEST_F(BufferedDataSourceTest, ReadCacheHit) { + InitializeDataSource(kHttpUrl, net::OK, true, 25, LOADING); + + // Performs read with cache hit. + ReadDataSourceHit(10, 10, 10); + + // Performs read with cache hit but partially filled. + ReadDataSourceHit(20, 10, 5); + + StopDataSource(); +} + +TEST_F(BufferedDataSourceTest, ReadCacheMiss) { + InitializeDataSource(kHttpUrl, net::OK, true, 1024, LOADING); + ReadDataSourceMiss(1000, 10); + ReadDataSourceMiss(20, 10); + StopDataSource(); +} + +TEST_F(BufferedDataSourceTest, ReadFailed) { + InitializeDataSource(kHttpUrl, net::OK, true, 1024, LOADING); + ReadDataSourceHit(10, 10, 10); + ReadDataSourceFailed(10, 10, net::ERR_CONNECTION_RESET); + StopDataSource(); +} + +TEST_F(BufferedDataSourceTest, ReadTimesOut) { + InitializeDataSource(kHttpUrl, net::OK, true, 1024, LOADING); + ReadDataSourceTimesOut(20, 10); + StopDataSource(); +} + +TEST_F(BufferedDataSourceTest, FileHasLoadedState) { + InitializeDataSource(kFileUrl, net::OK, true, 1024, LOADED); + ReadDataSourceTimesOut(20, 10); + StopDataSource(); +} + +} // namespace webkit_glue diff --git a/webkit/glue/media/media_resource_loader_bridge_factory.cc b/webkit/glue/media/media_resource_loader_bridge_factory.cc new file mode 100644 index 0000000..1961bcc --- /dev/null +++ b/webkit/glue/media/media_resource_loader_bridge_factory.cc @@ -0,0 +1,76 @@ +// 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 "webkit/glue/media/media_resource_loader_bridge_factory.h" + +#include "base/format_macros.h" +#include "base/string_util.h" + +namespace { + +// A constant for an unknown position. +const int64 kPositionNotSpecified = -1; + +} // namespace + +namespace webkit_glue { + +MediaResourceLoaderBridgeFactory::MediaResourceLoaderBridgeFactory( + const GURL& referrer, + const std::string& frame_origin, + const std::string& main_frame_origin, + int origin_pid, + int appcache_host_id, + int32 routing_id) + : referrer_(referrer), + frame_origin_(frame_origin), + main_frame_origin_(main_frame_origin), + origin_pid_(origin_pid), + appcache_host_id_(appcache_host_id), + routing_id_(routing_id) { +} + +ResourceLoaderBridge* MediaResourceLoaderBridgeFactory::CreateBridge( + const GURL& url, + int load_flags, + int64 first_byte_position, + int64 last_byte_position) { + webkit_glue::ResourceLoaderBridge::RequestInfo request_info; + request_info.method = "GET"; + request_info.url = url; + request_info.first_party_for_cookies = url; + request_info.referrer = referrer_; + request_info.frame_origin = frame_origin_; + request_info.main_frame_origin = main_frame_origin_; + request_info.headers = GenerateHeaders(first_byte_position, + last_byte_position); + request_info.load_flags = load_flags; + request_info.requestor_pid = origin_pid_; + request_info.request_type = ResourceType::MEDIA; + request_info.appcache_host_id = appcache_host_id_; + request_info.routing_id = routing_id_; + return webkit_glue::ResourceLoaderBridge::Create(request_info); +} + +// static +const std::string MediaResourceLoaderBridgeFactory::GenerateHeaders ( + int64 first_byte_position, int64 last_byte_position) { + // Construct the range header. + std::string header; + if (first_byte_position > kPositionNotSpecified && + last_byte_position > kPositionNotSpecified) { + if (first_byte_position <= last_byte_position) { + header = StringPrintf("Range: bytes=%" PRId64 "-%" PRId64, + first_byte_position, + last_byte_position); + } + } else if (first_byte_position > kPositionNotSpecified) { + header = StringPrintf("Range: bytes=%" PRId64 "-", first_byte_position); + } else if (last_byte_position > kPositionNotSpecified) { + NOTIMPLEMENTED() << "Suffix range not implemented"; + } + return header; +} + +} // namespace webkit_glue diff --git a/webkit/glue/media/media_resource_loader_bridge_factory.h b/webkit/glue/media/media_resource_loader_bridge_factory.h new file mode 100644 index 0000000..6408949 --- /dev/null +++ b/webkit/glue/media/media_resource_loader_bridge_factory.h @@ -0,0 +1,76 @@ +// 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 WEBKIT_GLUE_MEDIA_MEDIA_RESOURCE_LOADER_BRIDGE_FACTORY_H_ +#define WEBKIT_GLUE_MEDIA_MEDIA_RESOURCE_LOADER_BRIDGE_FACTORY_H_ + +#include "testing/gtest/include/gtest/gtest_prod.h" +#include "webkit/glue/resource_loader_bridge.h" + +namespace webkit_glue { + +// A factory used to create a ResourceLoaderBridge for the media player. +// This factory is used also for testing. Testing code can use this class and +// override CreateBridge() to inject a mock ResourceLoaderBridge for code that +// interacts with it, e.g. BufferedDataSource. +class MediaResourceLoaderBridgeFactory { + public: + MediaResourceLoaderBridgeFactory( + const GURL& referrer, + const std::string& frame_origin, + const std::string& main_frame_origin, + int origin_pid, + int appcache_host_id, + int32 routing_id); + + virtual ~MediaResourceLoaderBridgeFactory() {} + + // Factory method to create a ResourceLoaderBridge with the following + // parameters: + // |url| - URL of the resource to be loaded. + // |load_flags| - Load flags for this loading. + // |first_byte_position| - First byte position for a range request, -1 if not. + // |last_byte_position| - Last byte position for a range request, -1 if not. + virtual ResourceLoaderBridge* CreateBridge( + const GURL& url, + int load_flags, + int64 first_byte_position, + int64 last_byte_position); + + protected: + // An empty constructor only used by inherited classes. + MediaResourceLoaderBridgeFactory() { + } + + private: + FRIEND_TEST(MediaResourceLoaderBridgeFactoryTest, GenerateHeaders); + + // Returns a range request header using parameters |first_byte_position| and + // |last_byte_position|. + // Negative numbers other than -1 are not allowed for |first_byte_position| + // and |last_byte_position|. |first_byte_position| should always be less than + // or equal to |last_byte_position| if they are both not -1. + // Here's a list of valid parameters: + // |first_byte_position| |last_byte_position| + // 0 1000 + // 4096 4096 + // 0 -1 + // -1 -1 + // Empty string is returned on invalid parameters. + static const std::string GenerateHeaders(int64 first_byte_position, + int64 last_byte_position); + + GURL first_party_for_cookies_; + GURL referrer_; + std::string frame_origin_; + std::string main_frame_origin_; + std::string headers_; + int origin_pid_; + int appcache_host_id_; + int32 routing_id_; +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_MEDIA_MEDIA_RESOURCE_LOADER_BRIDGE_FACTORY_H_ diff --git a/webkit/glue/media/media_resource_loader_bridge_factory_unittest.cc b/webkit/glue/media/media_resource_loader_bridge_factory_unittest.cc new file mode 100644 index 0000000..4c0126b --- /dev/null +++ b/webkit/glue/media/media_resource_loader_bridge_factory_unittest.cc @@ -0,0 +1,44 @@ +// 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 "net/http/http_util.h" +#include "webkit/glue/media/media_resource_loader_bridge_factory.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace webkit_glue { + +TEST(MediaResourceLoaderBridgeFactoryTest, GenerateHeaders) { + static const struct { + const bool success; + const int64 first_byte_position; + const int64 last_byte_position; + } data[] = { + { false, -1, -1 }, + { false, -5, 0 }, + { false, 100, 0 }, + { true, 0, -1 }, + { true, 0, 0 }, + { true, 100, 100 }, + { true, 50, -1 }, + { true, 10000, -1 }, + { true, 50, 100 }, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(data); ++i) { + std::string headers = MediaResourceLoaderBridgeFactory::GenerateHeaders( + data[i].first_byte_position, data[i].last_byte_position); + std::vector ranges; + bool ret = net::HttpUtil::ParseRanges(headers, &ranges); + EXPECT_EQ(data[i].success, ret); + if (ret) { + EXPECT_EQ(1u, ranges.size()); + EXPECT_EQ(data[i].first_byte_position, + ranges[0].first_byte_position()); + EXPECT_EQ(data[i].last_byte_position, + ranges[0].last_byte_position()); + } + } +} + +} // namespace webkit_glue diff --git a/webkit/glue/media/mock_media_resource_loader_bridge_factory.h b/webkit/glue/media/mock_media_resource_loader_bridge_factory.h new file mode 100644 index 0000000..7bb27fe --- /dev/null +++ b/webkit/glue/media/mock_media_resource_loader_bridge_factory.h @@ -0,0 +1,36 @@ +// 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 WEBKIT_GLUE_MOCK_RESOURCE_LOADER_BRIDGE_H_ +#define WEBKIT_GLUE_MOCK_RESOURCE_LOADER_BRIDGE_H_ + +#include "testing/gmock/include/gmock/gmock.h" +#include "webkit/glue/media/media_resource_loader_bridge_factory.h" + +namespace webkit_glue { + +class MockMediaResourceLoaderBridgeFactory + : public webkit_glue::MediaResourceLoaderBridgeFactory { + public: + MockMediaResourceLoaderBridgeFactory() { + } + + virtual ~MockMediaResourceLoaderBridgeFactory() { + OnDestroy(); + } + + MOCK_METHOD4(CreateBridge, + webkit_glue::ResourceLoaderBridge*(const GURL& url, + int load_flags, + int64 first_byte_position, + int64 last_byte_position)); + MOCK_METHOD0(OnDestroy, void()); + + private: + DISALLOW_COPY_AND_ASSIGN(MockMediaResourceLoaderBridgeFactory); +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_MEDIA_MOCK_RESOURCE_LOADER_BRIDGE_H_ diff --git a/webkit/glue/media/simple_data_source.cc b/webkit/glue/media/simple_data_source.cc new file mode 100644 index 0000000..20bf0af --- /dev/null +++ b/webkit/glue/media/simple_data_source.cc @@ -0,0 +1,236 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/message_loop.h" +#include "base/process_util.h" +#include "media/base/filter_host.h" +#include "net/base/load_flags.h" +#include "net/base/data_url.h" +#include "net/http/http_response_headers.h" +#include "net/url_request/url_request_status.h" +#include "webkit/glue/media/simple_data_source.h" +#include "webkit/glue/resource_loader_bridge.h" +#include "webkit/glue/webkit_glue.h" + +namespace { + +const char kHttpScheme[] = "http"; +const char kHttpsScheme[] = "https"; +const char kDataScheme[] = "data"; + +// A helper method that accepts only HTTP, HTTPS and FILE protocol. +bool IsDataProtocol(const GURL& url) { + return url.SchemeIs(kDataScheme); +} + +} // namespace + +namespace webkit_glue { + +bool SimpleDataSource::IsMediaFormatSupported( + const media::MediaFormat& media_format) { + std::string mime_type; + std::string url; + if (media_format.GetAsString(media::MediaFormat::kMimeType, &mime_type) && + media_format.GetAsString(media::MediaFormat::kURL, &url)) { + GURL gurl(url); + if (IsProtocolSupportedForMedia(gurl)) + return true; + } + return false; +} + +SimpleDataSource::SimpleDataSource( + MessageLoop* render_loop, + webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory) + : render_loop_(render_loop), + bridge_factory_(bridge_factory), + size_(-1), + state_(UNINITIALIZED) { + DCHECK(render_loop); +} + +SimpleDataSource::~SimpleDataSource() { + AutoLock auto_lock(lock_); + DCHECK(state_ == UNINITIALIZED || state_ == STOPPED); +} + +void SimpleDataSource::Stop(media::FilterCallback* callback) { + AutoLock auto_lock(lock_); + state_ = STOPPED; + if (callback) { + callback->Run(); + delete callback; + } + + // Post a task to the render thread to cancel loading the resource. + render_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, &SimpleDataSource::CancelTask)); +} + +void SimpleDataSource::Initialize(const std::string& url, + media::FilterCallback* callback) { + AutoLock auto_lock(lock_); + DCHECK_EQ(state_, UNINITIALIZED); + DCHECK(callback); + state_ = INITIALIZING; + initialize_callback_.reset(callback); + + // Validate the URL. + SetURL(GURL(url)); + if (!url_.is_valid() || !IsProtocolSupportedForMedia(url_)) { + host()->SetError(media::PIPELINE_ERROR_NETWORK); + initialize_callback_->Run(); + initialize_callback_.reset(); + return; + } + + // Post a task to the render thread to start loading the resource. + render_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, &SimpleDataSource::StartTask)); +} + +const media::MediaFormat& SimpleDataSource::media_format() { + return media_format_; +} + +void SimpleDataSource::Read(int64 position, + size_t size, + uint8* data, + ReadCallback* read_callback) { + DCHECK_GE(size_, 0); + if (position >= size_) { + read_callback->RunWithParams(Tuple1(0)); + delete read_callback; + } else { + size_t copied = std::min(size, static_cast(size_ - position)); + memcpy(data, data_.c_str() + position, copied); + read_callback->RunWithParams(Tuple1(copied)); + delete read_callback; + } +} + +bool SimpleDataSource::GetSize(int64* size_out) { + *size_out = size_; + return true; +} + +bool SimpleDataSource::IsStreaming() { + return false; +} + +void SimpleDataSource::OnDownloadProgress(uint64 position, uint64 size) {} + +void SimpleDataSource::OnUploadProgress(uint64 position, uint64 size) {} + +bool SimpleDataSource::OnReceivedRedirect( + const GURL& new_url, + const webkit_glue::ResourceLoaderBridge::ResponseInfo& info, + bool* has_new_first_party_for_cookies, + GURL* new_first_party_for_cookies) { + SetURL(new_url); + // TODO(wtc): should we return a new first party for cookies URL? + *has_new_first_party_for_cookies = false; + return true; +} + +void SimpleDataSource::OnReceivedResponse( + const webkit_glue::ResourceLoaderBridge::ResponseInfo& info, + bool content_filtered) { + size_ = info.content_length; +} + +void SimpleDataSource::OnReceivedData(const char* data, int len) { + data_.append(data, len); +} + +void SimpleDataSource::OnCompletedRequest(const URLRequestStatus& status, + const std::string& security_info) { + AutoLock auto_lock(lock_); + // It's possible this gets called after Stop(), in which case |host_| is no + // longer valid. + if (state_ == STOPPED) { + return; + } + + // Otherwise we should be initializing and have created a bridge. + DCHECK_EQ(state_, INITIALIZING); + DCHECK(bridge_.get()); + bridge_.reset(); + + // If we don't get a content length or the request has failed, report it + // as a network error. + DCHECK(size_ == -1 || static_cast(size_) == data_.length()); + if (size_ == -1) { + size_ = data_.length(); + } + + DoneInitialization_Locked(status.is_success()); +} + +GURL SimpleDataSource::GetURLForDebugging() const { + return url_; +} + +void SimpleDataSource::SetURL(const GURL& url) { + url_ = url; + media_format_.Clear(); + media_format_.SetAsString(media::MediaFormat::kMimeType, + media::mime_type::kApplicationOctetStream); + media_format_.SetAsString(media::MediaFormat::kURL, url.spec()); +} + +void SimpleDataSource::StartTask() { + AutoLock auto_lock(lock_); + DCHECK(MessageLoop::current() == render_loop_); + + // We may have stopped. + if (state_ == STOPPED) + return; + + DCHECK_EQ(state_, INITIALIZING); + + if (IsDataProtocol(url_)) { + // If this using data protocol, we just need to decode it. + std::string mime_type, charset; + bool success = net::DataURL::Parse(url_, &mime_type, &charset, &data_); + + // Don't care about the mime-type just proceed if decoding was successful. + size_ = data_.length(); + DoneInitialization_Locked(success); + } else { + // Create our bridge and start loading the resource. + bridge_.reset(bridge_factory_->CreateBridge( + url_, net::LOAD_BYPASS_CACHE, -1, -1)); + bridge_->Start(this); + } +} + +void SimpleDataSource::CancelTask() { + AutoLock auto_lock(lock_); + DCHECK_EQ(state_, STOPPED); + + // Cancel any pending requests. + if (bridge_.get()) { + bridge_->Cancel(); + bridge_.reset(); + } +} + +void SimpleDataSource::DoneInitialization_Locked(bool success) { + lock_.AssertAcquired(); + if (success) { + state_ = INITIALIZED; + host()->SetTotalBytes(size_); + host()->SetBufferedBytes(size_); + // If scheme is file or data, say we are loaded. + host()->SetLoaded(url_.SchemeIsFile() || IsDataProtocol(url_)); + } else { + host()->SetError(media::PIPELINE_ERROR_NETWORK); + } + initialize_callback_->Run(); + initialize_callback_.reset(); +} + +} // namespace webkit_glue diff --git a/webkit/glue/media/simple_data_source.h b/webkit/glue/media/simple_data_source.h new file mode 100644 index 0000000..577d973 --- /dev/null +++ b/webkit/glue/media/simple_data_source.h @@ -0,0 +1,125 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// An extremely simple implementation of DataSource that downloads the entire +// media resource into memory before signaling that initialization has finished. +// Primarily used to test